]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
use pve-edk2-firmware for supporting OVMF
[qemu-server.git] / PVE / QemuServer.pm
index 1d1ae29310781cf81bfe2e1db75ba2ab93d30184..061effa9ea7c71f2aa96aa81a4f9c2b29c24384b 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 dir_glob_foreach);
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
 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;
@@ -33,17 +33,21 @@ use PVE::RPCEnvironment;
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::USB qw(parse_usb_device);
+use PVE::QemuServer::Cloudinit;
 use Time::HiRes qw(gettimeofday);
 use File::Copy qw(copy);
 use URI::Escape;
 
-my $OVMF_CODE = '/usr/share/kvm/OVMF_CODE-pure-efi.fd';
-my $OVMF_VARS = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
+my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
+my $OVMF_CODE = "$EDK2_FW_BASE/OVMF_CODE.fd";
+my $OVMF_VARS = "$EDK2_FW_BASE/OVMF_VARS.fd";
 
 my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
 
+my $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
+
 # Note about locking: we use flock on the config file protect
 # against concurent actions.
 # Aditionaly, we have a 'lock' setting in the config file. This
@@ -108,17 +112,28 @@ my $cpu_vendor_list = {
     coreduo => 'GenuineIntel',
     core2duo => 'GenuineIntel',
     Conroe  => 'GenuineIntel',
-    Penryn  => 'GenuineIntel', 
+    Penryn  => 'GenuineIntel',
     Nehalem  => 'GenuineIntel',
+    'Nehalem-IBRS'  => 'GenuineIntel',
     Westmere => 'GenuineIntel',
+    'Westmere-IBRS' => 'GenuineIntel',
     SandyBridge => 'GenuineIntel',
+    'SandyBridge-IBRS' => 'GenuineIntel',
     IvyBridge => 'GenuineIntel',
+    'IvyBridge-IBRS' => 'GenuineIntel',
     Haswell => 'GenuineIntel',
+    'Haswell-IBRS' => 'GenuineIntel',
     'Haswell-noTSX' => 'GenuineIntel',
+    'Haswell-noTSX-IBRS' => 'GenuineIntel',
     Broadwell => 'GenuineIntel',
+    'Broadwell-IBRS' => 'GenuineIntel',
     'Broadwell-noTSX' => 'GenuineIntel',
+    'Broadwell-noTSX-IBRS' => 'GenuineIntel',
     'Skylake-Client' => 'GenuineIntel',
-    
+    'Skylake-Client-IBRS' => 'GenuineIntel',
+    'Skylake-Server' => 'GenuineIntel',
+    'Skylake-Server-IBRS' => 'GenuineIntel',
+
     # AMD CPUs
     athlon => 'AuthenticAMD',
     phenom  => 'AuthenticAMD',
@@ -127,6 +142,8 @@ my $cpu_vendor_list = {
     Opteron_G3  => 'AuthenticAMD',
     Opteron_G4  => 'AuthenticAMD',
     Opteron_G5  => 'AuthenticAMD',
+    EPYC => 'AuthenticAMD',
+    'EPYC-IBPB' => 'AuthenticAMD',
 
     # generic types, use vendor from host node
     host => 'default',
@@ -134,8 +151,11 @@ my $cpu_vendor_list = {
     kvm64 => 'default',
     qemu32 => 'default',
     qemu64 => 'default',
+    max => 'default',
 };
 
+my $cpu_flag = qr/[+-](pcid|spec-ctrl)/;
+
 my $cpu_fmt = {
     cputype => {
        description => "Emulated CPU type.",
@@ -150,6 +170,15 @@ my $cpu_fmt = {
        optional => 1,
        default => 0
     },
+    flags => {
+       description => "List of additional CPU flags separated by ';'."
+                    . " Use '+FLAG' to enable, '-FLAG' to disable a flag."
+                    . " Currently supported flags: 'pcid', 'spec-ctrl'.",
+       format_description => '+FLAG[;-FLAG...]',
+       type => 'string',
+       pattern => qr/$cpu_flag(;$cpu_flag)*/,
+       optional => 1,
+    },
 };
 
 my $watchdog_fmt = {
@@ -511,6 +540,41 @@ EODESCR
     },
 };
 
+my $confdesc_cloudinit = {
+    citype => {
+       optional => 1,
+       type => 'string',
+       description => 'Specifies the cloud-init configuration format. The default depends on the configured operating system type (`ostype`. We use the `nocloud` format for Linux, and `configdrive2` for windows.',
+       enum => ['configdrive2', 'nocloud'],
+    },
+    ciuser => {
+       optional => 1,
+       type => 'string',
+       description => "cloud-init: User name to change ssh keys and password for instead of the image's configured default user.",
+    },
+    cipassword => {
+       optional => 1,
+       type => 'string',
+       description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.',
+    },
+    searchdomain => {
+       optional => 1,
+       type => 'string',
+       description => "cloud-init: Sets DNS search domains for a container. Create will automatically use the setting from the host if neither searchdomain nor nameserver are set.",
+    },
+    nameserver => {
+       optional => 1,
+       type => 'string', format => 'address-list',
+       description => "cloud-init: Sets DNS server IP address for a container. Create will automatically use the setting from the host if neither searchdomain nor nameserver are set.",
+    },
+    sshkeys => {
+       optional => 1,
+       type => 'string',
+       format => 'urlencoded',
+       description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).",
+    },
+};
+
 # what about other qemu settings ?
 #cpu => 'string',
 #machine => 'string',
@@ -668,8 +732,64 @@ my $netdesc = {
 
 PVE::JSONSchema::register_standard_option("pve-qm-net", $netdesc);
 
+my $ipconfig_fmt = {
+    ip => {
+       type => 'string',
+       format => 'pve-ipv4-config',
+       format_description => 'IPv4Format/CIDR',
+       description => 'IPv4 address in CIDR format.',
+       optional => 1,
+       default => 'dhcp',
+    },
+    gw => {
+       type => 'string',
+       format => 'ipv4',
+       format_description => 'GatewayIPv4',
+       description => 'Default gateway for IPv4 traffic.',
+       optional => 1,
+       requires => 'ip',
+    },
+    ip6 => {
+       type => 'string',
+       format => 'pve-ipv6-config',
+       format_description => 'IPv6Format/CIDR',
+       description => 'IPv6 address in CIDR format.',
+       optional => 1,
+       default => 'dhcp',
+    },
+    gw6 => {
+       type => 'string',
+       format => 'ipv6',
+       format_description => 'GatewayIPv6',
+       description => 'Default gateway for IPv6 traffic.',
+       optional => 1,
+       requires => 'ip6',
+    },
+};
+PVE::JSONSchema::register_format('pve-qm-ipconfig', $ipconfig_fmt);
+my $ipconfigdesc = {
+    optional => 1,
+    type => 'string', format => 'pve-qm-ipconfig',
+    description => <<'EODESCR',
+cloud-init: Specify IP addresses and gateways for the corresponding interface.
+
+IP addresses use CIDR notation, gateways are optional but need an IP of the same type specified.
+
+The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit gateway should be provided.
+For IPv6 the special string 'auto' can be used to use stateless autoconfiguration.
+
+If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4.
+EODESCR
+};
+PVE::JSONSchema::register_standard_option("pve-qm-ipconfig", $netdesc);
+
 for (my $i = 0; $i < $MAX_NETS; $i++)  {
     $confdesc->{"net$i"} = $netdesc;
+    $confdesc_cloudinit->{"ipconfig$i"} = $ipconfigdesc;
+}
+
+foreach my $key (keys %$confdesc_cloudinit) {
+    $confdesc->{$key} = $confdesc_cloudinit->{$key};
 }
 
 PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path);
@@ -730,7 +850,9 @@ my %drivedesc_base = (
     },
     snapshot => {
        type => 'boolean',
-       description => "Whether the drive should be included when making snapshots.",
+       description => "Controls qemu's snapshot mode feature."
+           . " If activated, changes made to the disk are temporary and will"
+           . " be discarded when the VM is shutdown.",
        optional => 1,
     },
     cache => {
@@ -794,6 +916,13 @@ my %drivedesc_base = (
        maxLength => 20*3, # *3 since it's %xx url enoded
        description => "The drive's reported serial number, url-encoded, up to 20 bytes long.",
        optional => 1,
+    },
+    shared => {
+       type => 'boolean',
+       description => 'Mark this locally-managed volume as available on all nodes',
+       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!",
+       optional => 1,
+       default => 0,
     }
 );
 
@@ -1243,7 +1372,7 @@ sub get_iso_path {
 sub filename_to_volume_id {
     my ($vmid, $file, $media) = @_;
 
-    if (!($file eq 'none' || $file eq 'cdrom' ||
+     if (!($file eq 'none' || $file eq 'cdrom' ||
          $file =~ m|^/dev/.+| || $file =~ m/^([^:]+):(.+)$/)) {
 
        return undef if $file =~ m|/|;
@@ -1599,10 +1728,17 @@ sub print_drive_full {
    }
 
     my $opts = '';
-    my @qemu_drive_options = qw(heads secs cyls trans media format cache snapshot rerror werror aio discard);
+    my @qemu_drive_options = qw(heads secs cyls trans media format cache rerror werror aio discard);
     foreach my $o (@qemu_drive_options) {
-       $opts .= ",$o=$drive->{$o}" if $drive->{$o};
+       $opts .= ",$o=$drive->{$o}" if defined($drive->{$o});
     }
+
+    # snapshot only accepts on|off
+    if (defined($drive->{snapshot})) {
+       my $v = $drive->{snapshot} ? 'on' : 'off';
+       $opts .= ",snapshot=$v";
+    }
+
     foreach my $type (['', '-total'], [_rd => '-read'], [_wr => '-write']) {
        my ($dir, $qmpname) = @$type;
        if (my $v = $drive->{"mbps$dir"}) {
@@ -1761,8 +1897,15 @@ sub print_cpu_device {
     return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
 }
 
-sub drive_is_cdrom {
+sub drive_is_cloudinit {
     my ($drive) = @_;
+    return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
+}
+
+sub drive_is_cdrom {
+    my ($drive, $exclude_cloudinit) = @_;
+
+    return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
 
     return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
 
@@ -1832,6 +1975,41 @@ sub parse_net {
     return $res;
 }
 
+# ipconfigX ip=cidr,gw=ip,ip6=cidr,gw6=ip
+sub parse_ipconfig {
+    my ($data) = @_;
+
+    my $res = eval { PVE::JSONSchema::parse_property_string($ipconfig_fmt, $data) };
+    if ($@) {
+       warn $@;
+       return undef;
+    }
+
+    if ($res->{gw} && !$res->{ip}) {
+       warn 'gateway specified without specifying an IP address';
+       return undef;
+    }
+    if ($res->{gw6} && !$res->{ip6}) {
+       warn 'IPv6 gateway specified without specifying an IPv6 address';
+       return undef;
+    }
+    if ($res->{gw} && $res->{ip} eq 'dhcp') {
+       warn 'gateway specified together with DHCP';
+       return undef;
+    }
+    if ($res->{gw6} && $res->{ip6} !~ /^$IPV6RE/) {
+       # gw6 + auto/dhcp
+       warn "IPv6 gateway specified together with $res->{ip6} address";
+       return undef;
+    }
+
+    if (!$res->{ip} && !$res->{ip6}) {
+       return { ip => 'dhcp', ip6 => 'dhcp' };
+    }
+
+    return $res;
+}
+
 sub print_net {
     my $net = shift;
 
@@ -1900,7 +2078,10 @@ sub vmconfig_undelete_pending_option {
 sub vmconfig_register_unused_drive {
     my ($storecfg, $vmid, $conf, $drive) = @_;
 
-    if (!drive_is_cdrom($drive)) {
+    if (drive_is_cloudinit($drive)) {
+       eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) };
+       warn $@ if $@;
+    } elsif (!drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
        if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
            PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);
@@ -2051,6 +2232,12 @@ sub json_config_properties {
     return $prop;
 }
 
+# return copy of $confdesc_cloudinit to generate documentation
+sub cloudinit_config_properties {
+
+    return dclone($confdesc_cloudinit);
+}
+
 sub check_type {
     my ($key, $value) = @_;
 
@@ -2134,7 +2321,7 @@ sub destroy_vm {
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
 
-       return if drive_is_cdrom($drive);
+       return if drive_is_cdrom($drive, 1);
 
        my $volid = $drive->{file};
 
@@ -2239,7 +2426,7 @@ sub parse_vm_config {
            } else {
                warn "vm $vmid - propertry 'delete' is only allowed in [PENDING]\n";
            }
-       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
+       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(.+?)\s*$/) {
            my $key = $1;
            my $value = $2;
            eval { $value = check_type($key, $value); };
@@ -2386,9 +2573,6 @@ sub load_defaults {
        }
     }
 
-    my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
-    $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard};
-
     return $res;
 }
 
@@ -2591,6 +2775,8 @@ sub vmstatus {
     my $storecfg = PVE::Storage::config();
 
     my $list = vzlist();
+    my $defaults = load_defaults();
+
     my ($uptime) = PVE::ProcFSTools::read_proc_uptime(1);
 
     my $cpucount = $cpuinfo->{cpus} || 1;
@@ -2616,16 +2802,19 @@ sub vmstatus {
            $d->{maxdisk} = 0;
        }
 
-       $d->{cpus} = ($conf->{sockets} || 1) * ($conf->{cores} || 1);
+       $d->{cpus} = ($conf->{sockets} || $defaults->{sockets})
+           * ($conf->{cores} || $defaults->{cores});
        $d->{cpus} = $cpucount if $d->{cpus} > $cpucount;
        $d->{cpus} = $conf->{vcpus} if $conf->{vcpus};
 
        $d->{name} = $conf->{name} || "VM $vmid";
-       $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024) : 0;
+       $d->{maxmem} = $conf->{memory} ? $conf->{memory}*(1024*1024)
+           : $defaults->{memory}*(1024*1024);
 
        if ($conf->{balloon}) {
            $d->{balloon_min} = $conf->{balloon}*(1024*1024);
-           $d->{shares} = defined($conf->{shares}) ? $conf->{shares} : 1000;
+           $d->{shares} = defined($conf->{shares}) ? $conf->{shares}
+               : $defaults->{shares};
        }
 
        $d->{uptime} = 0;
@@ -2640,6 +2829,8 @@ sub vmstatus {
 
         $d->{template} = PVE::QemuConfig->is_template($conf);
 
+       $d->{serial} = 1 if conf_has_serial($conf);
+
        $res->{$vmid} = $d;
     }
 
@@ -2798,7 +2989,7 @@ sub foreach_volid {
     my $volhash = {};
 
     my $test_volid = sub {
-       my ($volid, $is_cdrom, $replicate, $snapname) = @_;
+       my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
 
        return if !$volid;
 
@@ -2808,6 +2999,9 @@ sub foreach_volid {
        $volhash->{$volid}->{replicate} //= 0;
        $volhash->{$volid}->{replicate} = 1 if $replicate;
 
+       $volhash->{$volid}->{shared} //= 0;
+       $volhash->{$volid}->{shared} = 1 if $shared;
+
        $volhash->{$volid}->{referenced_in_config} //= 0;
        $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname);
 
@@ -2817,7 +3011,7 @@ sub foreach_volid {
 
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
-       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, undef);
+       $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef);
     });
 
     foreach my $snapname (keys %{$conf->{snapshots}}) {
@@ -2825,7 +3019,7 @@ sub foreach_volid {
        $test_volid->($snap->{vmstate}, 0, 1, $snapname);
        foreach_drive($snap, sub {
            my ($ds, $drive) = @_;
-           $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $snapname);
+           $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, $snapname);
         });
     }
 
@@ -2834,6 +3028,18 @@ sub foreach_volid {
     }
 }
 
+sub conf_has_serial {
+    my ($conf) = @_;
+
+    for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++)  {
+       if ($conf->{"serial$i"}) {
+           return 1;
+       }
+    }
+
+    return 0;
+}
+
 sub vga_conf_has_spice {
     my ($vga) = @_;
 
@@ -2884,6 +3090,10 @@ sub config_to_command {
 
     push @$cmd, '-id', $vmid;
 
+    my $vmname = $conf->{name} || "vm$vmid";
+
+    push @$cmd, '-name', $vmname;
+
     my $use_virtio = 0;
 
     my $qmpsocket = qmp_socket($vmid);
@@ -2903,20 +3113,27 @@ sub config_to_command {
        die "uefi base image not found\n" if ! -f $OVMF_CODE;
 
        my $path;
-       my $format = 'raw';
+       my $format;
        if (my $efidisk = $conf->{efidisk0}) {
            my $d = PVE::JSONSchema::parse_property_string($efidisk_fmt, $efidisk);
            my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
+           $format = $d->{format};
            if ($storeid) {
                $path = PVE::Storage::path($storecfg, $d->{file});
+               if (!defined($format)) {
+                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+                   $format = qemu_img_format($scfg, $volname);
+               }
            } else {
                $path = $d->{file};
+               die "efidisk format must be specified\n"
+                   if !defined($format);
            }
-           $format = $d->{format} if $d->{format};
        } else {
            warn "no efidisk configured! Using temporary efivars disk.\n";
            $path = "/tmp/$vmid-ovmf.fd";
            PVE::Tools::file_copy($OVMF_VARS, $path, -s $OVMF_VARS);
+           $format = 'raw';
        }
 
        push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$OVMF_CODE";
@@ -3033,9 +3250,6 @@ sub config_to_command {
        }
     }
 
-    my $vmname = $conf->{name} || "vm$vmid";
-
-    push @$cmd, '-name', $vmname;
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -3131,6 +3345,10 @@ sub config_to_command {
            or die "Cannot parse cpu description: $cputype\n";
        $cpu = $cpuconf->{cputype};
        $kvm_off = 1 if $cpuconf->{hidden};
+
+       if (defined(my $flags = $cpuconf->{flags})) {
+           push @$cpuFlags, split(";", $flags);
+       }
     }
 
     push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64';
@@ -3168,9 +3386,7 @@ sub config_to_command {
     
     push @$cmd, '-S' if $conf->{freeze};
 
-    # set keyboard layout
-    my $kb = $conf->{keyboard} || $defaults->{keyboard};
-    push @$cmd, '-k', $kb if $kb;
+    push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
 
     # enable sound
     #my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
@@ -3570,10 +3786,6 @@ sub vm_deviceunplug {
 
     } elsif ($deviceid =~ m/^(scsi)(\d+)$/) {
 
-       #qemu 2.3 segfault on drive_del with virtioscsi + iothread
-       my $device = parse_drive($deviceid, $conf->{$deviceid});
-       die "virtioscsi with iothread is not hot-unplugglable currently" if $device->{iothread};
-
         qemu_devicedel($vmid, $deviceid);
         qemu_drivedel($vmid, $deviceid);
        qemu_deletescsihw($conf, $vmid, $deviceid);
@@ -3952,81 +4164,6 @@ sub __read_avail {
     return $res;
 }
 
-# old code, only used to shutdown old VM after update
-sub vm_monitor_command {
-    my ($vmid, $cmdstr, $nocheck) = @_;
-
-    my $res;
-
-    eval {
-       die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
-
-       my $sname = "${var_run_tmpdir}/$vmid.mon";
-
-       my $sock = IO::Socket::UNIX->new( Peer => $sname ) ||
-           die "unable to connect to VM $vmid socket - $!\n";
-
-       my $timeout = 3;
-
-       # hack: migrate sometime blocks the monitor (when migrate_downtime
-       # is set)
-       if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
-           $timeout = 60*60; # 1 hour
-       }
-
-       # read banner;
-       my $data = __read_avail($sock, $timeout);
-
-       if ($data !~ m/^QEMU\s+(\S+)\s+monitor\s/) {
-           die "got unexpected qemu monitor banner\n";
-       }
-
-       my $sel = new IO::Select;
-       $sel->add($sock);
-
-       if (!scalar(my @ready = $sel->can_write($timeout))) {
-           die "monitor write error - timeout";
-       }
-
-       my $fullcmd = "$cmdstr\r";
-
-       # syslog('info', "VM $vmid monitor command: $cmdstr");
-
-       my $b;
-       if (!($b = $sock->syswrite($fullcmd)) || ($b != length($fullcmd))) {
-           die "monitor write error - $!";
-       }
-
-       return if ($cmdstr eq 'q') || ($cmdstr eq 'quit');
-
-       $timeout = 20;
-
-       if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
-           $timeout = 60*60; # 1 hour
-       } elsif ($cmdstr =~ m/^(eject|change)/) {
-           $timeout = 60; # note: cdrom mount command is slow
-       }
-       if ($res = __read_avail($sock, $timeout)) {
-
-           my @lines = split("\r?\n", $res);
-
-           shift @lines if $lines[0] !~ m/^unknown command/; # skip echo
-
-           $res = join("\n", @lines);
-           $res .= "\n";
-       }
-    };
-
-    my $err = $@;
-
-    if ($err) {
-       syslog("err", "VM $vmid monitor command failed - $err");
-       die $err;
-    }
-
-    return $res;
-}
-
 sub qemu_block_resize {
     my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
 
@@ -4190,6 +4327,22 @@ sub vmconfig_hotplug_pending {
        }
     }
 
+    my $apply_pending_cloudinit;
+    $apply_pending_cloudinit = sub {
+       my ($key, $value) = @_;
+       $apply_pending_cloudinit = sub {}; # once is enough
+
+       my @cloudinit_opts = keys %$confdesc_cloudinit;
+       foreach my $opt (keys %{$conf->{pending}}) {
+           next if !grep { $_ eq $opt } @cloudinit_opts;
+           $conf->{$opt} = delete $conf->{pending}->{$opt};
+       }
+
+       my $new_conf = { %$conf };
+       $new_conf->{$key} = $value;
+       PVE::QemuServer::Cloudinit::generate_cloudinitconfig($new_conf, $vmid);
+    };
+
     foreach my $opt (keys %{$conf->{pending}}) {
        next if $selection && !$selection->{$opt};
        my $value = $conf->{pending}->{$opt};
@@ -4231,6 +4384,10 @@ sub vmconfig_hotplug_pending {
                                    $vmid, $opt, $value);
            } elsif (is_valid_drivename($opt)) {
                # some changes can be done without hotplug
+               my $drive = parse_drive($opt, $value);
+               if (drive_is_cloudinit($drive)) {
+                   &$apply_pending_cloudinit($opt, $value);
+               }
                vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
                                     $vmid, $opt, $value, 1);
            } elsif ($opt =~ m/^memory$/) { #dimms
@@ -4493,6 +4650,9 @@ sub vmconfig_update_disk {
 
                if ($drive->{file} eq 'none') {
                    vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
+                   if (drive_is_cloudinit($old_drive)) {
+                       vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);
+                   }
                } else {
                    my $path = get_iso_path($storecfg, $vmid, $drive->{file});
                    vm_mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
@@ -4528,6 +4688,8 @@ sub vm_start {
            $conf = PVE::QemuConfig->load_config($vmid); # update/reload
        }
 
+       PVE::QemuServer::Cloudinit::generate_cloudinitconfig($conf, $vmid);
+
        my $defaults = load_defaults();
 
        # set environment variable useful inside network script
@@ -4653,16 +4815,18 @@ sub vm_start {
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       if (!check_running($vmid, 1) && -d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
-           my $cmd = [];
-           push @$cmd, '/bin/systemctl', 'stop', "$vmid.scope";
-           eval  { run_command($cmd); };
+       if (!check_running($vmid, 1)) {
+           eval {
+               run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
+                   outfunc => sub {}, errfunc => sub {});
+           };
        }
 
        my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
                                                  : $defaults->{cpuunits};
 
-       my %run_params = (timeout => $statefile ? undef : 30, umask => 0077);
+       my $start_timeout = $conf->{hugepages} ? 300 : 30;
+       my %run_params = (timeout => $statefile ? undef : $start_timeout, umask => 0077);
 
        my %properties = (
            Slice => 'qemu.slice',
@@ -4805,10 +4969,6 @@ sub vm_qmp_command {
            my $qmpclient = PVE::QMPClient->new();
 
            $res = $qmpclient->cmd($vmid, $cmd, $timeout);
-       } elsif (-e "${var_run_tmpdir}/$vmid.mon") {
-           die "can't execute complex command on old monitor - stop/start your vm to fix the problem\n"
-               if scalar(%{$cmd->{arguments}});
-           vm_monitor_command($vmid, $cmd->{execute}, $nocheck);
        } else {
            die "unable to open monitor socket\n";
        }
@@ -5293,6 +5453,13 @@ sub restore_update_config_line {
        } else {
            print $outfd $line;
        }
+    } elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) {
+       my ($uuid, $uuid_str);
+       UUID::generate($uuid);
+       UUID::unparse($uuid, $uuid_str);
+       my $smbios1 = parse_smbios1($2);
+       $smbios1->{uuid} = $uuid_str;
+       print $outfd $1.print_smbios1($smbios1)."\n";
     } else {
        print $outfd $line;
     }
@@ -5469,21 +5636,49 @@ sub rescan {
 sub restore_vma_archive {
     my ($archive, $vmid, $user, $opts, $comp) = @_;
 
-    my $input = $archive eq '-' ? "<&STDIN" : undef;
     my $readfrom = $archive;
 
-    my $uncomp = '';
-    if ($comp) {
+    my $cfg = PVE::Storage::config();
+    my $commands = [];
+    my $bwlimit = $opts->{bwlimit};
+
+    my $dbg_cmdstring = '';
+    my $add_pipe = sub {
+       my ($cmd) = @_;
+       push @$commands, $cmd;
+       $dbg_cmdstring .= ' | ' if length($dbg_cmdstring);
+       $dbg_cmdstring .= PVE::Tools::cmd2string($cmd);
        $readfrom = '-';
-       my $qarchive = PVE::Tools::shellquote($archive);
+    };
+
+    my $input = undef;
+    if ($archive eq '-') {
+       $input = '<&STDIN';
+    } else {
+       # If we use a backup from a PVE defined storage we also consider that
+       # storage's rate limit:
+       my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive);
+       if (defined($volid)) {
+           my ($sid, undef) = PVE::Storage::parse_volume_id($volid);
+           my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit);
+           if ($readlimit) {
+               print STDERR "applying read rate limit: $readlimit\n";
+               my $cstream = ['cstream', '-t', $readlimit*1024, '--', $readfrom];
+               $add_pipe->($cstream);
+           }
+       }
+    }
+
+    if ($comp) {
+       my $cmd;
        if ($comp eq 'gzip') {
-           $uncomp = "zcat $qarchive|";
+           $cmd = ['zcat', $readfrom];
        } elsif ($comp eq 'lzop') {
-           $uncomp = "lzop -d -c $qarchive|";
+           $cmd = ['lzop', '-d', '-c', $readfrom];
        } else {
            die "unknown compression method '$comp'\n";
        }
-
+       $add_pipe->($cmd);
     }
 
     my $tmpdir = "/var/tmp/vzdumptmp$$";
@@ -5503,7 +5698,7 @@ sub restore_vma_archive {
        open($fifofh, '>', $mapfifo) || die $!;
     };
 
-    my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+    $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
 
     my $oldtimeout;
     my $timeout = 5;
@@ -5519,6 +5714,8 @@ sub restore_vma_archive {
     my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
     my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
 
+    my %storage_limits;
+
     my $print_devmap = sub {
        my $virtdev_hash = {};
 
@@ -5557,17 +5754,24 @@ sub restore_vma_archive {
                    $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
                }
 
+               $storage_limits{$storeid} = $bwlimit;
+
                $virtdev_hash->{$virtdev} = $devinfo->{$devname};
            }
        }
 
+       foreach my $key (keys %storage_limits) {
+           my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit);
+           next if !$limit;
+           print STDERR "rate limit for storage $key: $limit KiB/s\n";
+           $storage_limits{$key} = $limit * 1024;
+       }
+
        foreach my $devname (keys %$devinfo) {
            die "found no device mapping information for device '$devname'\n"
                if !$devinfo->{$devname}->{virtdev};
        }
 
-       my $cfg = PVE::Storage::config();
-
        # create empty/temp config
        if ($oldconf) {
            PVE::Tools::file_set_contents($conffile, "memory: 128\n");
@@ -5610,14 +5814,20 @@ sub restore_vma_archive {
        foreach my $virtdev (sort keys %$virtdev_hash) {
            my $d = $virtdev_hash->{$virtdev};
            my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
-           my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+           my $storeid = $d->{storeid};
+           my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+
+           my $map_opts = '';
+           if (my $limit = $storage_limits{$storeid}) {
+               $map_opts .= "throttling.bps=$limit:throttling.group=$storeid:";
+           }
 
            # test if requested format is supported
-           my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+           my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $storeid);
            my $supported = grep { $_ eq $d->{format} } @$validFormats;
            $d->{format} = $defFormat if !$supported;
 
-           my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
+           my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
                                                  $d->{format}, undef, $alloc_size);
            print STDERR "new volume ID is '$volid'\n";
            $d->{volid} = $volid;
@@ -5630,7 +5840,7 @@ sub restore_vma_archive {
                $write_zeros = 0;
            }
 
-           print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
+           print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
 
            print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
            $map->{$virtdev} = $volid;
@@ -5683,8 +5893,8 @@ sub restore_vma_archive {
            }
        };
 
-       print "restore vma archive: $cmd\n";
-       run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+       print "restore vma archive: $dbg_cmdstring\n";
+       run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);
     };
     my $err = $@;
 
@@ -5696,7 +5906,6 @@ sub restore_vma_archive {
        push @$vollist, $volid if $volid;
     }
 
-    my $cfg = PVE::Storage::config();
     PVE::Storage::deactivate_volumes($cfg, $vollist);
 
     unlink $mapfifo;
@@ -5965,7 +6174,7 @@ sub qemu_img_convert {
 sub qemu_img_format {
     my ($scfg, $volname) = @_;
 
-    if ($scfg->{path} && $volname =~ m/\.(raw|cow|qcow|qcow2|qed|vmdk|cloop)$/) {
+    if ($scfg->{path} && $volname =~ m/\.($QEMU_FORMAT_RE)$/) {
        return $1;
     } else {
        return "raw";
@@ -5981,32 +6190,9 @@ sub qemu_drive_mirror {
     my $format;
     $jobs->{"drive-$drive"} = {};
 
-    if ($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+):exportname=(\S+)/) {
-       my $server = $1;
-       my $port = $2;
-       my $exportname = $3;
-
+    if ($dst_volid =~ /^nbd:/) {
+       $qemu_target = $dst_volid;
        $format = "nbd";
-       my $unixsocket = "/run/qemu-server/$vmid.mirror-drive-$drive";
-       $qemu_target = "nbd+unix:///$exportname?socket=$unixsocket";
-       my $cmd = ['socat', '-T30', "UNIX-LISTEN:$unixsocket,fork", "TCP:$server:$2,connect-timeout=5"];
-
-       my $pid = fork();
-       if (!defined($pid)) {
-           die "forking socat tunnel failed\n";
-       } elsif ($pid == 0) {
-           exec(@$cmd);
-           warn "exec failed: $!\n";
-           POSIX::_exit(-1);
-       }
-       $jobs->{"drive-$drive"}->{pid} = $pid;
-
-       my $timeout = 0;
-       while (!-S $unixsocket) {
-           die "nbd connection helper timed out\n"
-               if $timeout++ > 5;
-           sleep 1;
-       }
     } else {
        my $storecfg = PVE::Storage::config();
        my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
@@ -6118,7 +6304,6 @@ sub qemu_drive_mirror_monitor {
                        }else {
                            print "$job: Completed successfully.\n";
                            $jobs->{$job}->{complete} = 1;
-                           eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
                        }
                    }
                }
@@ -6156,7 +6341,6 @@ sub qemu_blockjobs_cancel {
 
            if (defined($jobs->{$job}->{cancel}) && !defined($running_jobs->{$job})) {
                print "$job: Done.\n";
-               eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
                delete $jobs->{$job};
            }
        }
@@ -6167,25 +6351,6 @@ sub qemu_blockjobs_cancel {
     }
 }
 
-sub qemu_blockjobs_finish_tunnel {
-   my ($vmid, $job, $cpid) = @_;
-
-   return if !$cpid;
-
-   for (my $i = 1; $i < 20; $i++) {
-       my $waitpid = waitpid($cpid, WNOHANG);
-       last if (defined($waitpid) && ($waitpid == $cpid));
-       if ($i == 10) {
-           kill(15, $cpid);
-       } elsif ($i >= 15) {
-           kill(9, $cpid);
-       }
-       sleep (1);
-    }
-    unlink "/run/qemu-server/$vmid.mirror-$job";
-}
-
 sub clone_disk {
     my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
        $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
@@ -6205,7 +6370,17 @@ sub clone_disk {
        my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
 
        print "create full clone of drive $drivename ($drive->{file})\n";
-       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, undef, ($size/1024));
+       my $name = undef;
+       if (drive_is_cloudinit($drive)) {
+           $name = "vm-$newvmid-cloudinit";
+           # cloudinit only supports raw and qcow2 atm:
+           if ($dst_format eq 'qcow2') {
+               $name .= '.qcow2';
+           } elsif ($dst_format ne 'raw') {
+               die "clone: unhandled format for cloudinit image\n";
+           }
+       }
+       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
        push @$newvollist, $newvolid;
 
        PVE::Storage::activate_volumes($storecfg, [$newvolid]);
@@ -6547,10 +6722,4 @@ sub complete_storage {
     return $res;
 }
 
-sub nbd_stop {
-    my ($vmid) = @_;
-
-    vm_mon_cmd($vmid, 'nbd-server-stop');
-}
-
 1;