+ }
+
+ $d->{mem} = 0;
+ $d->{swap} = 0;
+ $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
+ $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
+
+ $d->{uptime} = 0;
+ $d->{cpu} = 0;
+
+ $d->{netout} = 0;
+ $d->{netin} = 0;
+
+ $d->{diskread} = 0;
+ $d->{diskwrite} = 0;
+
+ $d->{template} = is_template($conf);
+ }
+
+ foreach my $vmid (keys %$list) {
+ my $d = $list->{$vmid};
+ my $pid = $d->{pid};
+
+ next if !$pid; # skip stopped CTs
+
+ my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
+ $d->{uptime} = time - $ctime; # the method lxcfs uses
+
+ $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
+ $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
+
+ my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
+ my @bytes = split(/\n/, $blkio_bytes);
+ foreach my $byte (@bytes) {
+ if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
+ $d->{diskread} = $2 if $key eq 'Read';
+ $d->{diskwrite} = $2 if $key eq 'Write';
+ }
+ }
+
+ my $pstat = &$parse_cpuacct_stat($vmid);
+
+ my $used = $pstat->{utime} + $pstat->{stime};
+
+ my $old = $last_proc_vmid_stat->{$vmid};
+ if (!$old) {
+ $last_proc_vmid_stat->{$vmid} = {
+ time => $cdtime,
+ used => $used,
+ cpu => 0,
+ };
+ next;
+ }
+
+ my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz};
+
+ if ($dtime > 1000) {
+ my $dutime = $used - $old->{used};
+
+ $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus};
+ $last_proc_vmid_stat->{$vmid} = {
+ time => $cdtime,
+ used => $used,
+ cpu => $d->{cpu},
+ };
+ } else {
+ $d->{cpu} = $old->{cpu};
+ }
+ }
+
+ my $netdev = PVE::ProcFSTools::read_proc_net_dev();
+
+ foreach my $dev (keys %$netdev) {
+ next if $dev !~ m/^veth([1-9]\d*)i/;
+ my $vmid = $1;
+ my $d = $list->{$vmid};
+
+ next if !$d;
+
+ $d->{netout} += $netdev->{$dev}->{receive};
+ $d->{netin} += $netdev->{$dev}->{transmit};
+
+ }
+
+ return $list;
+}
+
+sub classify_mountpoint {
+ my ($vol) = @_;
+ if ($vol =~ m!^/!) {
+ return 'device' if $vol =~ m!^/dev/!;
+ return 'bind';
+ }
+ return 'volume';
+}
+
+my $parse_ct_mountpoint_full = sub {
+ my ($desc, $data, $noerr) = @_;
+
+ $data //= '';
+
+ my $res;
+ eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
+ if ($@) {
+ return undef if $noerr;
+ die $@;
+ }
+
+ if (defined(my $size = $res->{size})) {
+ $size = PVE::JSONSchema::parse_size($size);
+ if (!defined($size)) {
+ return undef if $noerr;
+ die "invalid size: $size\n";
+ }
+ $res->{size} = $size;
+ }
+
+ $res->{type} = classify_mountpoint($res->{volume});
+
+ return $res;
+};
+
+sub parse_ct_rootfs {
+ my ($data, $noerr) = @_;
+
+ my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
+
+ $res->{mp} = '/' if defined($res);
+
+ return $res;
+}
+
+sub parse_ct_mountpoint {
+ my ($data, $noerr) = @_;
+
+ return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
+}
+
+sub print_ct_mountpoint {
+ my ($info, $nomp) = @_;
+ my $skip = [ 'type' ];
+ push @$skip, 'mp' if $nomp;
+ return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
+}
+
+sub print_lxc_network {
+ my $net = shift;
+ return PVE::JSONSchema::print_property_string($net, $netconf_desc);
+}
+
+sub parse_lxc_network {
+ my ($data) = @_;
+
+ my $res = {};
+
+ return $res if !$data;
+
+ $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
+
+ $res->{type} = 'veth';
+ $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
+
+ return $res;
+}
+
+sub read_cgroup_value {
+ my ($group, $vmid, $name, $full) = @_;
+
+ my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
+
+ return PVE::Tools::file_get_contents($path) if $full;
+
+ return PVE::Tools::file_read_firstline($path);
+}
+
+sub write_cgroup_value {
+ my ($group, $vmid, $name, $value) = @_;
+
+ my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
+ PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
+
+}
+
+sub find_lxc_console_pids {
+
+ my $res = {};
+
+ PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
+ my ($pid) = @_;
+
+ my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
+ return if !$cmdline;
+
+ my @args = split(/\0/, $cmdline);
+
+ # search for lxc-console -n <vmid>
+ return if scalar(@args) != 3;
+ return if $args[1] ne '-n';
+ return if $args[2] !~ m/^\d+$/;
+ return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
+
+ my $vmid = $args[2];
+
+ push @{$res->{$vmid}}, $pid;
+ });
+
+ return $res;
+}
+
+sub find_lxc_pid {
+ my ($vmid) = @_;
+
+ my $pid = undef;
+ my $parser = sub {
+ my $line = shift;
+ $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
+ };
+ PVE::Tools::run_command(['lxc-info', '-n', $vmid, '-p'], outfunc => $parser);
+
+ die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
+
+ return $pid;
+}
+
+# Note: we cannot use Net:IP, because that only allows strict
+# CIDR networks
+sub parse_ipv4_cidr {
+ my ($cidr, $noerr) = @_;
+
+ if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
+ return { address => $1, netmask => $PVE::Network::ipv4_reverse_mask->[$2] };
+ }
+
+ return undef if $noerr;
+
+ die "unable to parse ipv4 address/mask\n";
+}
+
+sub check_lock {
+ my ($conf) = @_;
+
+ die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
+}
+
+sub check_protection {
+ my ($vm_conf, $err_msg) = @_;
+
+ if ($vm_conf->{protection}) {
+ die "$err_msg - protection mode enabled\n";
+ }
+}
+
+sub update_lxc_config {
+ my ($storage_cfg, $vmid, $conf) = @_;
+
+ my $dir = "/var/lib/lxc/$vmid";
+
+ if ($conf->{template}) {
+
+ unlink "$dir/config";
+
+ return;
+ }
+
+ my $raw = '';
+
+ die "missing 'arch' - internal error" if !$conf->{arch};
+ $raw .= "lxc.arch = $conf->{arch}\n";
+
+ my $unprivileged = $conf->{unprivileged};
+ my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
+
+ my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
+ if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) {
+ $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
+ if ($unprivileged || $custom_idmap) {
+ $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
+ }
+ } else {
+ die "implement me (ostype $ostype)";
+ }
+
+ $raw .= "lxc.monitor.unshare = 1\n";
+
+ # Should we read them from /etc/subuid?
+ if ($unprivileged && !$custom_idmap) {
+ $raw .= "lxc.id_map = u 0 100000 65536\n";
+ $raw .= "lxc.id_map = g 0 100000 65536\n";
+ }
+
+ if (!has_dev_console($conf)) {
+ $raw .= "lxc.console = none\n";
+ $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
+ }
+
+ my $ttycount = get_tty_count($conf);
+ $raw .= "lxc.tty = $ttycount\n";
+
+ # some init scripts expect a linux terminal (turnkey).
+ $raw .= "lxc.environment = TERM=linux\n";
+
+ my $utsname = $conf->{hostname} || "CT$vmid";
+ $raw .= "lxc.utsname = $utsname\n";
+
+ my $memory = $conf->{memory} || 512;
+ my $swap = $conf->{swap} // 0;
+
+ my $lxcmem = int($memory*1024*1024);
+ $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
+
+ my $lxcswap = int(($memory + $swap)*1024*1024);
+ $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
+
+ if (my $cpulimit = $conf->{cpulimit}) {
+ $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
+ my $value = int(100000*$cpulimit);
+ $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
+ }
+
+ my $shares = $conf->{cpuunits} || 1024;
+ $raw .= "lxc.cgroup.cpu.shares = $shares\n";
+
+ my $mountpoint = parse_ct_rootfs($conf->{rootfs});
+
+ $raw .= "lxc.rootfs = $dir/rootfs\n";
+
+ my $netcount = 0;
+ foreach my $k (keys %$conf) {
+ next if $k !~ m/^net(\d+)$/;
+ my $ind = $1;
+ my $d = parse_lxc_network($conf->{$k});
+ $netcount++;
+ $raw .= "lxc.network.type = veth\n";
+ $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
+ $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
+ $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
+ $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
+ }
+
+ if (my $lxcconf = $conf->{lxc}) {
+ foreach my $entry (@$lxcconf) {
+ my ($k, $v) = @$entry;
+ $netcount++ if $k eq 'lxc.network.type';
+ $raw .= "$k = $v\n";
+ }
+ }
+
+ $raw .= "lxc.network.type = empty\n" if !$netcount;
+
+ File::Path::mkpath("$dir/rootfs");
+
+ PVE::Tools::file_set_contents("$dir/config", $raw);
+}
+
+# verify and cleanup nameserver list (replace \0 with ' ')
+sub verify_nameserver_list {
+ my ($nameserver_list) = @_;
+
+ my @list = ();
+ foreach my $server (PVE::Tools::split_list($nameserver_list)) {
+ PVE::JSONSchema::pve_verify_ip($server);
+ push @list, $server;
+ }
+
+ return join(' ', @list);
+}
+
+sub verify_searchdomain_list {
+ my ($searchdomain_list) = @_;
+
+ my @list = ();
+ foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
+ # todo: should we add checks for valid dns domains?
+ push @list, $server;
+ }
+
+ return join(' ', @list);
+}
+
+sub add_unused_volume {
+ my ($config, $volid) = @_;
+
+ my $key;
+ for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
+ my $test = "unused$ind";
+ if (my $vid = $config->{$test}) {
+ return if $vid eq $volid; # do not add duplicates
+ } else {
+ $key = $test;
+ }
+ }
+
+ die "Too many unused volumes - please delete them first.\n" if !$key;
+
+ $config->{$key} = $volid;
+
+ return $key;
+}
+
+sub update_pct_config {
+ my ($vmid, $conf, $running, $param, $delete) = @_;
+
+ my @nohotplug;
+
+ my $new_disks = 0;
+ my @deleted_volumes;
+
+ my $rootdir;
+ if ($running) {
+ my $pid = find_lxc_pid($vmid);
+ $rootdir = "/proc/$pid/root";
+ }
+
+ my $hotplug_error = sub {
+ if ($running) {
+ push @nohotplug, @_;
+ return 1;
+ } else {
+ return 0;
+ }
+ };
+
+ if (defined($delete)) {
+ foreach my $opt (@$delete) {
+ if (!exists($conf->{$opt})) {
+ warn "no such option: $opt\n";