]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
improve error message on container without rootfs
[pve-container.git] / src / PVE / LXC.pm
index f5cc1f26b3d626be80fe643a9573ac7f2d13cc92..f7122620f149edf239162bbb7bfda28c56c817c2 100644 (file)
@@ -2,6 +2,7 @@ package PVE::LXC;
 
 use strict;
 use warnings;
+
 use POSIX qw(EINTR);
 
 use Socket;
@@ -11,16 +12,15 @@ use File::Spec;
 use Cwd qw();
 use Fcntl qw(O_RDONLY);
 
-use PVE::Cluster qw(cfs_register_file cfs_read_file);
 use PVE::Exception qw(raise_perm_exc);
 use PVE::Storage;
 use PVE::SafeSyslog;
 use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
 use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
 use PVE::Network;
 use PVE::AccessControl;
 use PVE::ProcFSTools;
+use PVE::LXC::Config;
 use Time::HiRes qw (gettimeofday);
 
 use Data::Dumper;
@@ -35,547 +35,6 @@ our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
                           '--xattrs-include=security.capability',
                           '--warning=no-xattr-write' ];
 
-cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
-
-my $rootfs_desc = {
-    volume => {
-       type => 'string',
-       default_key => 1,
-       format => 'pve-lxc-mp-string',
-       format_description => 'volume',
-       description => 'Volume, device or directory to mount into the container.',
-    },
-    backup => {
-       type => 'boolean',
-       format_description => '[1|0]',
-       description => 'Whether to include the mountpoint in backups.',
-       optional => 1,
-    },
-    size => {
-       type => 'string',
-       format => 'disk-size',
-       format_description => 'DiskSize',
-       description => 'Volume size (read only value).',
-       optional => 1,
-    },
-    acl => {
-       type => 'boolean',
-       format_description => 'acl',
-       description => 'Explicitly enable or disable ACL support.',
-       optional => 1,
-    },
-    ro => {
-       type => 'boolean',
-       format_description => 'ro',
-       description => 'Read-only mountpoint (not supported with bind mounts)',
-       optional => 1,
-    },
-    quota => {
-       type => 'boolean',
-       format_description => '[0|1]',
-       description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
-       optional => 1,
-    },
-};
-
-PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
-    type => 'string', format => $rootfs_desc,
-    description => "Use volume as container root.",
-    optional => 1,
-});
-
-PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
-    description => "The name of the snapshot.",
-    type => 'string', format => 'pve-configid',
-    maxLength => 40,
-});
-
-my $confdesc = {
-    lock => {
-       optional => 1,
-       type => 'string',
-       description => "Lock/unlock the VM.",
-       enum => [qw(migrate backup snapshot rollback)],
-    },
-    onboot => {
-       optional => 1,
-       type => 'boolean',
-       description => "Specifies whether a VM will be started during system bootup.",
-       default => 0,
-    },
-    startup => get_standard_option('pve-startup-order'),
-    template => {
-       optional => 1,
-       type => 'boolean',
-       description => "Enable/disable Template.",
-       default => 0,
-    },
-    arch => {
-       optional => 1,
-       type => 'string',
-       enum => ['amd64', 'i386'],
-       description => "OS architecture type.",
-       default => 'amd64',
-    },
-    ostype => {
-       optional => 1,
-       type => 'string',
-       enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
-       description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
-    },
-    console => {
-       optional => 1,
-       type => 'boolean',
-       description => "Attach a console device (/dev/console) to the container.",
-       default => 1,
-    },
-    tty => {
-       optional => 1,
-       type => 'integer',
-       description => "Specify the number of tty available to the container",
-       minimum => 0,
-       maximum => 6,
-       default => 2,
-    },
-    cpulimit => {
-       optional => 1,
-       type => 'number',
-       description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
-       minimum => 0,
-       maximum => 128,
-       default => 0,
-    },
-    cpuunits => {
-       optional => 1,
-       type => 'integer',
-       description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
-       minimum => 0,
-       maximum => 500000,
-       default => 1024,
-    },
-    memory => {
-       optional => 1,
-       type => 'integer',
-       description => "Amount of RAM for the VM in MB.",
-       minimum => 16,
-       default => 512,
-    },
-    swap => {
-       optional => 1,
-       type => 'integer',
-       description => "Amount of SWAP for the VM in MB.",
-       minimum => 0,
-       default => 512,
-    },
-    hostname => {
-       optional => 1,
-       description => "Set a host name for the container.",
-       type => 'string', format => 'dns-name',
-       maxLength => 255,
-    },
-    description => {
-       optional => 1,
-       type => 'string',
-       description => "Container description. Only used on the configuration web interface.",
-    },
-    searchdomain => {
-       optional => 1,
-       type => 'string', format => 'dns-name-list',
-       description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
-    },
-    nameserver => {
-       optional => 1,
-       type => 'string', format => 'address-list',
-       description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
-    },
-    rootfs => get_standard_option('pve-ct-rootfs'),
-    parent => {
-       optional => 1,
-       type => 'string', format => 'pve-configid',
-       maxLength => 40,
-       description => "Parent snapshot name. This is used internally, and should not be modified.",
-    },
-    snaptime => {
-       optional => 1,
-       description => "Timestamp for snapshots.",
-       type => 'integer',
-       minimum => 0,
-    },
-    cmode => {
-       optional => 1,
-       description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
-       type => 'string',
-       enum => ['shell', 'console', 'tty'],
-       default => 'tty',
-    },
-    protection => {
-       optional => 1,
-       type => 'boolean',
-       description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
-       default => 0,
-    },
-    unprivileged => {
-       optional => 1,
-       type => 'boolean',
-       description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
-       default => 0,
-    },
-};
-
-my $valid_lxc_conf_keys = {
-    'lxc.include' => 1,
-    'lxc.arch' => 1,
-    'lxc.utsname' => 1,
-    'lxc.haltsignal' => 1,
-    'lxc.rebootsignal' => 1,
-    'lxc.stopsignal' => 1,
-    'lxc.init_cmd' => 1,
-    'lxc.network.type' => 1,
-    'lxc.network.flags' => 1,
-    'lxc.network.link' => 1,
-    'lxc.network.mtu' => 1,
-    'lxc.network.name' => 1,
-    'lxc.network.hwaddr' => 1,
-    'lxc.network.ipv4' => 1,
-    'lxc.network.ipv4.gateway' => 1,
-    'lxc.network.ipv6' => 1,
-    'lxc.network.ipv6.gateway' => 1,
-    'lxc.network.script.up' => 1,
-    'lxc.network.script.down' => 1,
-    'lxc.pts' => 1,
-    'lxc.console.logfile' => 1,
-    'lxc.console' => 1,
-    'lxc.tty' => 1,
-    'lxc.devttydir' => 1,
-    'lxc.hook.autodev' => 1,
-    'lxc.autodev' => 1,
-    'lxc.kmsg' => 1,
-    'lxc.mount' => 1,
-    'lxc.mount.entry' => 1,
-    'lxc.mount.auto' => 1,
-    'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
-    'lxc.rootfs.mount' => 1,
-    'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
-                            ', please use mountpoint options in the "rootfs" key',
-    # lxc.cgroup.*
-    'lxc.cap.drop' => 1,
-    'lxc.cap.keep' => 1,
-    'lxc.aa_profile' => 1,
-    'lxc.aa_allow_incomplete' => 1,
-    'lxc.se_context' => 1,
-    'lxc.seccomp' => 1,
-    'lxc.id_map' => 1,
-    'lxc.hook.pre-start' => 1,
-    'lxc.hook.pre-mount' => 1,
-    'lxc.hook.mount' => 1,
-    'lxc.hook.start' => 1,
-    'lxc.hook.stop' => 1,
-    'lxc.hook.post-stop' => 1,
-    'lxc.hook.clone' => 1,
-    'lxc.hook.destroy' => 1,
-    'lxc.loglevel' => 1,
-    'lxc.logfile' => 1,
-    'lxc.start.auto' => 1,
-    'lxc.start.delay' => 1,
-    'lxc.start.order' => 1,
-    'lxc.group' => 1,
-    'lxc.environment' => 1,
-};
-
-my $netconf_desc = {
-    type => {
-       type => 'string',
-       optional => 1,
-       description => "Network interface type.",
-       enum => [qw(veth)],
-    },
-    name => {
-       type => 'string',
-       format_description => 'String',
-       description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
-       pattern => '[-_.\w\d]+',
-    },
-    bridge => {
-       type => 'string',
-       format_description => 'vmbr<Number>',
-       description => 'Bridge to attach the network device to.',
-       pattern => '[-_.\w\d]+',
-       optional => 1,
-    },
-    hwaddr => {
-       type => 'string',
-       format_description => 'MAC',
-       description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
-       pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
-       optional => 1,
-    },
-    mtu => {
-       type => 'integer',
-       format_description => 'Number',
-       description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
-       minimum => 64, # minimum ethernet frame is 64 bytes
-       optional => 1,
-    },
-    ip => {
-       type => 'string',
-       format => 'pve-ipv4-config',
-       format_description => 'IPv4Format/CIDR',
-       description => 'IPv4 address in CIDR format.',
-       optional => 1,
-    },
-    gw => {
-       type => 'string',
-       format => 'ipv4',
-       format_description => 'GatewayIPv4',
-       description => 'Default gateway for IPv4 traffic.',
-       optional => 1,
-    },
-    ip6 => {
-       type => 'string',
-       format => 'pve-ipv6-config',
-       format_description => 'IPv6Format/CIDR',
-       description => 'IPv6 address in CIDR format.',
-       optional => 1,
-    },
-    gw6 => {
-       type => 'string',
-       format => 'ipv6',
-       format_description => 'GatewayIPv6',
-       description => 'Default gateway for IPv6 traffic.',
-       optional => 1,
-    },
-    firewall => {
-       type => 'boolean',
-       format_description => '[1|0]',
-       description => "Controls whether this interface's firewall rules should be used.",
-       optional => 1,
-    },
-    tag => {
-       type => 'integer',
-       format_description => 'VlanNo',
-       minimum => '2',
-       maximum => '4094',
-       description => "VLAN tag for this interface.",
-       optional => 1,
-    },
-    trunks => {
-       type => 'string',
-       pattern => qr/\d+(?:;\d+)*/,
-       format_description => 'vlanid[;vlanid...]',
-       description => "VLAN ids to pass through the interface",
-       optional => 1,
-    },
-};
-PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
-
-my $MAX_LXC_NETWORKS = 10;
-for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
-    $confdesc->{"net$i"} = {
-       optional => 1,
-       type => 'string', format => $netconf_desc,
-       description => "Specifies network interfaces for the container.",
-    };
-}
-
-PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
-sub verify_lxc_mp_string{
-    my ($mp, $noerr) = @_;
-
-    # do not allow:
-    # /./ or /../ 
-    # /. or /.. at the end
-    # ../ at the beginning
-    
-    if($mp =~ m@/\.\.?/@ ||
-       $mp =~ m@/\.\.?$@ ||
-       $mp =~ m@^\.\./@){
-       return undef if $noerr;
-       die "$mp contains illegal character sequences\n";
-    }
-    return $mp;
-}
-
-my $mp_desc = {
-    %$rootfs_desc,
-    mp => {
-       type => 'string',
-       format => 'pve-lxc-mp-string',
-       format_description => 'Path',
-       description => 'Path to the mountpoint as seen from inside the container.',
-    },
-};
-PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
-
-my $unuseddesc = {
-    optional => 1,
-    type => 'string', format => 'pve-volume-id',
-    description => "Reference to unused volumes.",
-};
-
-my $MAX_MOUNT_POINTS = 10;
-for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
-    $confdesc->{"mp$i"} = {
-       optional => 1,
-       type => 'string', format => $mp_desc,
-       description => "Use volume as container mount point (experimental feature).",
-       optional => 1,
-    };
-}
-
-my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
-for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
-    $confdesc->{"unused$i"} = $unuseddesc;
-}
-
-sub write_pct_config {
-    my ($filename, $conf) = @_;
-
-    delete $conf->{snapstate}; # just to be sure
-
-    my $generate_raw_config = sub {
-       my ($conf) = @_;
-
-       my $raw = '';
-
-       # add description as comment to top of file
-       my $descr = $conf->{description} || '';
-       foreach my $cl (split(/\n/, $descr)) {
-           $raw .= '#' .  PVE::Tools::encode_text($cl) . "\n";
-       }
-
-       foreach my $key (sort keys %$conf) {
-           next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || 
-               $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
-           my $value = $conf->{$key};
-           die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
-           $raw .= "$key: $value\n";
-       }
-
-       if (my $lxcconf = $conf->{lxc}) {
-           foreach my $entry (@$lxcconf) {
-               my ($k, $v) = @$entry;
-               $raw .= "$k: $v\n";
-           }
-       }
-       
-       return $raw;
-    };
-
-    my $raw = &$generate_raw_config($conf);
-
-    foreach my $snapname (sort keys %{$conf->{snapshots}}) {
-       $raw .= "\n[$snapname]\n";
-       $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
-    }
-
-    return $raw;
-}
-
-sub check_type {
-    my ($key, $value) = @_;
-
-    die "unknown setting '$key'\n" if !$confdesc->{$key};
-
-    my $type = $confdesc->{$key}->{type};
-
-    if (!defined($value)) {
-       die "got undefined value\n";
-    }
-
-    if ($value =~ m/[\n\r]/) {
-       die "property contains a line feed\n";
-    }
-
-    if ($type eq 'boolean') {
-       return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
-       return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
-       die "type check ('boolean') failed - got '$value'\n";
-    } elsif ($type eq 'integer') {
-       return int($1) if $value =~ m/^(\d+)$/;
-       die "type check ('integer') failed - got '$value'\n";
-    } elsif ($type eq 'number') {
-        return $value if $value =~ m/^(\d+)(\.\d+)?$/;
-        die "type check ('number') failed - got '$value'\n";
-    } elsif ($type eq 'string') {
-       if (my $fmt = $confdesc->{$key}->{format}) {
-           PVE::JSONSchema::check_format($fmt, $value);
-           return $value;
-       }
-       return $value;
-    } else {
-       die "internal error"
-    }
-}
-
-sub parse_pct_config {
-    my ($filename, $raw) = @_;
-
-    return undef if !defined($raw);
-
-    my $res = {
-       digest => Digest::SHA::sha1_hex($raw),
-       snapshots => {},
-    };
-
-    $filename =~ m|/lxc/(\d+).conf$|
-       || die "got strange filename '$filename'";
-
-    my $vmid = $1;
-
-    my $conf = $res;
-    my $descr = '';
-    my $section = '';
-
-    my @lines = split(/\n/, $raw);
-    foreach my $line (@lines) {
-       next if $line =~ m/^\s*$/;
-
-       if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
-           $section = $1;
-           $conf->{description} = $descr if $descr;
-           $descr = '';
-           $conf = $res->{snapshots}->{$section} = {};
-           next;
-       }
-
-       if ($line =~ m/^\#(.*)\s*$/) {
-           $descr .= PVE::Tools::decode_text($1) . "\n";
-           next;
-       }
-
-       if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
-           my $key = $1;
-           my $value = $3;
-           my $validity = $valid_lxc_conf_keys->{$key} || 0;
-           if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
-               push @{$conf->{lxc}}, [$key, $value];
-           } elsif (my $errmsg = $validity) {
-               warn "vm $vmid - $key: $errmsg\n";
-           } else {
-               warn "vm $vmid - unable to parse config: $line\n";
-           }
-       } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
-           $descr .= PVE::Tools::decode_text($2);
-       } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
-           $conf->{snapstate} = $1;
-       } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
-           my $key = $1;
-           my $value = $2;
-           eval { $value = check_type($key, $value); };
-           warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
-           $conf->{$key} = $value;
-       } else {
-           warn "vm $vmid - unable to parse config: $line\n";
-       }
-    }
-
-    $conf->{description} = $descr if $descr;
-
-    delete $res->{snapstate}; # just to be sure
-
-    return $res;
-}
-
 sub config_list {
     my $vmlist = PVE::Cluster::get_vmlist();
     my $res = {};
@@ -592,118 +51,10 @@ sub config_list {
     return $res;
 }
 
-sub cfs_config_path {
-    my ($vmid, $node) = @_;
-
-    $node = $nodename if !$node;
-    return "nodes/$node/lxc/$vmid.conf";
-}
-
-sub config_file {
-    my ($vmid, $node) = @_;
-
-    my $cfspath = cfs_config_path($vmid, $node);
-    return "/etc/pve/$cfspath";
-}
-
-sub load_config {
-    my ($vmid, $node) = @_;
-
-    $node = $nodename if !$node;
-    my $cfspath = cfs_config_path($vmid, $node);
-
-    my $conf = PVE::Cluster::cfs_read_file($cfspath);
-    die "container $vmid does not exist\n" if !defined($conf);
-
-    return $conf;
-}
-
-sub create_config {
-    my ($vmid, $conf) = @_;
-
-    my $dir = "/etc/pve/nodes/$nodename/lxc";
-    mkdir $dir;
-
-    write_config($vmid, $conf);
-}
-
 sub destroy_config {
     my ($vmid) = @_;
 
-    unlink config_file($vmid, $nodename);
-}
-
-sub write_config {
-    my ($vmid, $conf) = @_;
-
-    my $cfspath = cfs_config_path($vmid);
-
-    PVE::Cluster::cfs_write_file($cfspath, $conf);
-}
-
-# flock: we use one file handle per process, so lock file
-# can be called multiple times and will succeed for the same process.
-
-my $lock_handles =  {};
-my $lockdir = "/run/lock/lxc";
-
-sub config_file_lock {
-    my ($vmid) = @_;
-
-    return "$lockdir/pve-config-${vmid}.lock";
-}
-
-sub lock_config_full {
-    my ($vmid, $timeout, $code, @param) = @_;
-
-    my $filename = config_file_lock($vmid);
-
-    mkdir $lockdir if !-d $lockdir;
-
-    my $res = lock_file($filename, $timeout, $code, @param);
-
-    die $@ if $@;
-
-    return $res;
-}
-
-sub lock_config_mode {
-    my ($vmid, $timeout, $shared, $code, @param) = @_;
-
-    my $filename = config_file_lock($vmid);
-
-    mkdir $lockdir if !-d $lockdir;
-
-    my $res = lock_file_full($filename, $timeout, $shared, $code, @param);
-
-    die $@ if $@;
-
-    return $res;
-}
-
-sub lock_config {
-    my ($vmid, $code, @param) = @_;
-
-    return lock_config_full($vmid, 10, $code, @param);
-}
-
-sub option_exists {
-    my ($name) = @_;
-
-    return defined($confdesc->{$name});
-}
-
-# add JSON properties for create and set function
-sub json_config_properties {
-    my $prop = shift;
-
-    foreach my $opt (keys %$confdesc) {
-       next if $opt eq 'parent' || $opt eq 'snaptime';
-       next if $prop->{$opt};
-       $prop->{$opt} = $confdesc->{$opt};
-    }
-
-    return $prop;
+    unlink PVE::LXC::Config->config_file($vmid, $nodename);
 }
 
 # container status helpers
@@ -719,7 +70,7 @@ sub list_active_containers {
     return $res if !$fh;
 
     while (defined(my $line = <$fh>)) {
-       if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
+       if ($line =~ m/^[a-f0-9]+:\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\d+\s+(\S+)$/) {
            my $path = $1;
            if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
                $res->{$1} = 1;
@@ -789,7 +140,7 @@ sub vmstatus {
 
        $d->{status} = $d->{pid} ? 'running' : 'stopped';
 
-       my $cfspath = cfs_config_path($vmid);
+       my $cfspath = PVE::LXC::Config->cfs_config_path($vmid);
        my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
 
        $d->{name} = $conf->{'hostname'} || "CT$vmid";
@@ -797,6 +148,8 @@ sub vmstatus {
 
        $d->{cpus} = $conf->{cpulimit} || $cpucount;
 
+       $d->{lock} = $conf->{lock} || '';
+
        if ($d->{pid}) {
            my $res = get_container_disk_usage($vmid, $d->{pid});
            $d->{disk} = $res->{used};
@@ -805,8 +158,8 @@ sub vmstatus {
            $d->{disk} = 0;
            # use 4GB by default ??
            if (my $rootfs = $conf->{rootfs}) {
-               my $rootinfo = parse_ct_rootfs($rootfs);
-               $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
+               my $rootinfo = PVE::LXC::Config->parse_ct_rootfs($rootfs);
+               $d->{maxdisk} = $rootinfo->{size} || (4*1024*1024*1024);
            } else {
                $d->{maxdisk} = 4*1024*1024*1024;
            }
@@ -826,7 +179,7 @@ sub vmstatus {
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
 
-       $d->{template} = is_template($conf);
+       $d->{template} = PVE::LXC::Config->is_template($conf);
     }
 
     foreach my $vmid (keys %$list) {
@@ -897,84 +250,6 @@ sub vmstatus {
     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) = @_;
 
@@ -1048,24 +323,6 @@ sub parse_ipv4_cidr {
     die "unable to parse ipv4 address/mask\n";
 }
 
-sub check_lock {
-    my ($conf) = @_;
-
-    die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
-}
-
-sub has_lock {
-    my ($conf, $lock) = @_;
-    return $conf->{lock} && (!defined($lock) || $lock eq $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) = @_;
@@ -1114,12 +371,12 @@ sub update_lxc_config {
        $raw .= "lxc.id_map = g 0 100000 65536\n";
     }
 
-    if (!has_dev_console($conf)) {
+    if (!PVE::LXC::Config->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);
+    my $ttycount = PVE::LXC::Config->get_tty_count($conf);
     $raw .= "lxc.tty = $ttycount\n";
 
     # some init scripts expect a linux terminal (turnkey).
@@ -1146,7 +403,10 @@ sub update_lxc_config {
     my $shares = $conf->{cpuunits} || 1024;
     $raw .= "lxc.cgroup.cpu.shares = $shares\n";
 
-    my $mountpoint = parse_ct_rootfs($conf->{rootfs});
+    die "missing 'rootfs' configuration\n"
+       if !defined($conf->{rootfs});
+
+    my $mountpoint = PVE::LXC::Config->parse_ct_rootfs($conf->{rootfs});
 
     $raw .= "lxc.rootfs = $dir/rootfs\n";
 
@@ -1154,7 +414,7 @@ sub update_lxc_config {
     foreach my $k (keys %$conf) {
        next if $k !~ m/^net(\d+)$/;
        my $ind = $1;
-       my $d = parse_lxc_network($conf->{$k});
+       my $d = PVE::LXC::Config->parse_lxc_network($conf->{$k});
        $netcount++;
        $raw .= "lxc.network.type = veth\n";
        $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
@@ -1203,279 +463,10 @@ sub verify_searchdomain_list {
     return join(' ', @list);
 }
 
-sub is_volume_in_use {
-    my ($config, $volid, $include_snapshots) = @_;
-    my $used = 0;
-
-    foreach_mountpoint($config, sub {
-       my ($ms, $mountpoint) = @_;
-       return if $used;
-       if ($mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid) {
-           $used = 1;
-       }
-    });
-
-    my $snapshots = $config->{snapshots};
-    if ($include_snapshots && $snapshots) {
-       foreach my $snap (keys %$snapshots) {
-           $used ||= is_volume_in_use($snapshots->{$snap}, $volid);
-       }
-    }
-
-    return $used;
-}
-
-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";
-               next;
-           }
-
-           if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
-               die "unable to delete required option '$opt'\n";
-           } elsif ($opt eq 'swap') {
-               delete $conf->{$opt};
-               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
-           } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
-               delete $conf->{$opt};
-           } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
-                    $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
-               next if $hotplug_error->($opt);
-               delete $conf->{$opt};
-           } elsif ($opt =~ m/^net(\d)$/) {
-               delete $conf->{$opt};
-               next if !$running;
-               my $netid = $1;
-               PVE::Network::veth_delete("veth${vmid}i$netid");
-           } elsif ($opt eq 'protection') {
-               delete $conf->{$opt};
-           } elsif ($opt =~ m/^unused(\d+)$/) {
-               next if $hotplug_error->($opt);
-               check_protection($conf, "can't remove CT $vmid drive '$opt'");
-               push @deleted_volumes, $conf->{$opt};
-               delete $conf->{$opt};
-           } elsif ($opt =~ m/^mp(\d+)$/) {
-               next if $hotplug_error->($opt);
-               check_protection($conf, "can't remove CT $vmid drive '$opt'");
-               my $mp = parse_ct_mountpoint($conf->{$opt});
-               delete $conf->{$opt};
-               if ($mp->{type} eq 'volume' && !is_volume_in_use($conf, $mp->{volume})) {
-                   add_unused_volume($conf, $mp->{volume});
-               }
-           } elsif ($opt eq 'unprivileged') {
-               die "unable to delete read-only option: '$opt'\n";
-           } else {
-               die "implement me (delete: $opt)"
-           }
-           write_config($vmid, $conf) if $running;
-       }
-    }
-
-    # There's no separate swap size to configure, there's memory and "total"
-    # memory (iow. memory+swap). This means we have to change them together.
-    my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
-    my $wanted_swap =  PVE::Tools::extract_param($param, 'swap');
-    if (defined($wanted_memory) || defined($wanted_swap)) {
-
-       my $old_memory = ($conf->{memory} || 512);
-       my $old_swap = ($conf->{swap} || 0);
-
-       $wanted_memory //= $old_memory;
-       $wanted_swap //= $old_swap;
-
-        my $total = $wanted_memory + $wanted_swap;
-       if ($running) {
-           my $old_total = $old_memory + $old_swap;
-           if ($total > $old_total) {
-               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
-               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
-           } else {
-               write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
-               write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
-           }
-       }
-       $conf->{memory} = $wanted_memory;
-       $conf->{swap} = $wanted_swap;
-
-       write_config($vmid, $conf) if $running;
-    }
-
-    my $used_volids = {};
-
-    foreach my $opt (keys %$param) {
-       my $value = $param->{$opt};
-       if ($opt eq 'hostname') {
-           $conf->{$opt} = $value;
-       } elsif ($opt eq 'onboot') {
-           $conf->{$opt} = $value ? 1 : 0;
-       } elsif ($opt eq 'startup') {
-           $conf->{$opt} = $value;
-       } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
-           next if $hotplug_error->($opt);
-           $conf->{$opt} = $value;
-       } elsif ($opt eq 'nameserver') {
-           next if $hotplug_error->($opt);
-           my $list = verify_nameserver_list($value);
-           $conf->{$opt} = $list;
-       } elsif ($opt eq 'searchdomain') {
-           next if $hotplug_error->($opt);
-           my $list = verify_searchdomain_list($value);
-           $conf->{$opt} = $list;
-       } elsif ($opt eq 'cpulimit') {
-           next if $hotplug_error->($opt); # FIXME: hotplug
-           $conf->{$opt} = $value;
-       } elsif ($opt eq 'cpuunits') {
-           $conf->{$opt} = $value;
-           write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
-       } elsif ($opt eq 'description') {
-           $conf->{$opt} = PVE::Tools::encode_text($value);
-       } elsif ($opt =~ m/^net(\d+)$/) {
-           my $netid = $1;
-           my $net = parse_lxc_network($value);
-           if (!$running) {
-               $conf->{$opt} = print_lxc_network($net);
-           } else {
-               update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
-           }
-       } elsif ($opt eq 'protection') {
-           $conf->{$opt} = $value ? 1 : 0;
-        } elsif ($opt =~ m/^mp(\d+)$/) {
-           next if $hotplug_error->($opt);
-           check_protection($conf, "can't update CT $vmid drive '$opt'");
-           my $old = $conf->{$opt};
-           $conf->{$opt} = $value;
-           if (defined($old)) {
-               my $mp = parse_ct_mountpoint($old);
-               if ($mp->{type} eq 'volume' && !is_volume_in_use($conf, $mp->{volume})) {
-                   add_unused_volume($conf, $mp->{volume});
-               }
-           }
-           $new_disks = 1;
-           my $mp = parse_ct_mountpoint($value);
-           $used_volids->{$mp->{volume}} = 1;
-        } elsif ($opt eq 'rootfs') {
-           next if $hotplug_error->($opt);
-           check_protection($conf, "can't update CT $vmid drive '$opt'");
-           my $old = $conf->{$opt};
-           $conf->{$opt} = $value;
-           if (defined($old)) {
-               my $mp = parse_ct_rootfs($old);
-               if ($mp->{type} eq 'volume' && !is_volume_in_use($conf, $mp->{volume})) {
-                   add_unused_volume($conf, $mp->{volume});
-               }
-           }
-           my $mp = parse_ct_rootfs($value);
-           $used_volids->{$mp->{volume}} = 1;
-       } elsif ($opt eq 'unprivileged') {
-           die "unable to modify read-only option: '$opt'\n";
-       } elsif ($opt eq 'ostype') {
-           next if $hotplug_error->($opt);
-           $conf->{$opt} = $value;
-       } else {
-           die "implement me: $opt";
-       }
-       write_config($vmid, $conf) if $running;
-    }
-
-    # Cleanup config:
-
-    # Remove unused disks after re-adding
-    foreach my $key (keys %$conf) {
-       next if $key !~ /^unused\d+/;
-       my $volid = $conf->{$key};
-       if ($used_volids->{$volid}) {
-           delete $conf->{$key};
-       }
-    }
-
-    # Apply deletions and creations of new volumes
-    if (@deleted_volumes) {
-       my $storage_cfg = PVE::Storage::config();
-       foreach my $volume (@deleted_volumes) {
-           next if $used_volids->{$volume}; # could have been re-added, too
-           # also check for references in snapshots
-           next if is_volume_in_use($conf, $volume, 1);
-           delete_mountpoint_volume($storage_cfg, $vmid, $volume);
-       }
-    }
-
-    if ($new_disks) {
-       my $storage_cfg = PVE::Storage::config();
-       create_disks($storage_cfg, $vmid, $conf, $conf);
-    }
-
-    # This should be the last thing we do here
-    if ($running && scalar(@nohotplug)) {
-       die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
-    }
-}
-
-sub has_dev_console {
-    my ($conf) = @_;
-
-    return !(defined($conf->{console}) && !$conf->{console});
-}
-       
-sub get_tty_count {
-    my ($conf) = @_;
-
-    return $conf->{tty} // $confdesc->{tty}->{default};
-}
-
-sub get_cmode {
-    my ($conf) = @_;
-
-    return $conf->{cmode} // $confdesc->{cmode}->{default};
-}
-
 sub get_console_command {
     my ($vmid, $conf) = @_;
 
-    my $cmode = get_cmode($conf);
+    my $cmode = PVE::LXC::Config->get_cmode($conf);
 
     if ($cmode eq 'console') {
        return ['lxc-console', '-n',  $vmid, '-t', 0];
@@ -1494,7 +485,7 @@ sub get_primary_ips {
     # return data from net0
 
     return undef if !defined($conf->{net0});
-    my $net = parse_lxc_network($conf->{net0});
+    my $net = PVE::LXC::Config->parse_lxc_network($conf->{net0});
 
     my $ipv4 = $net->{ip};
     if ($ipv4) {
@@ -1519,7 +510,7 @@ sub get_primary_ips {
 sub delete_mountpoint_volume {
     my ($storage_cfg, $vmid, $volume) = @_;
 
-    return if classify_mountpoint($volume) ne 'volume';
+    return if PVE::LXC::Config->classify_mountpoint($volume) ne 'volume';
 
     my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
     PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
@@ -1528,7 +519,7 @@ sub delete_mountpoint_volume {
 sub destroy_lxc_container {
     my ($storage_cfg, $vmid, $conf) = @_;
 
-    foreach_mountpoint($conf, sub {
+    PVE::LXC::Config->foreach_mountpoint($conf, sub {
        my ($ms, $mountpoint) = @_;
        delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
     });
@@ -1548,7 +539,7 @@ sub vm_stop_cleanup {
     eval {
        if (!$keepActive) {
 
-            my $vollist = get_vm_volumes($conf);
+            my $vollist = PVE::LXC::Config->get_vm_volumes($conf);
            PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
        }
     };
@@ -1587,36 +578,44 @@ sub update_net {
     my $eth = $newnet->{name};
 
     if (my $oldnetcfg = $conf->{$opt}) {
-       my $oldnet = parse_lxc_network($oldnetcfg);
+       my $oldnet = PVE::LXC::Config->parse_lxc_network($oldnetcfg);
 
        if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
            &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
 
            PVE::Network::veth_delete($veth);
            delete $conf->{$opt};
-           write_config($vmid, $conf);
+           PVE::LXC::Config->write_config($vmid, $conf);
 
            hotplug_net($vmid, $conf, $opt, $newnet, $netid);
 
-       } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
-                &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
-                &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
+       } else {
+           if (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
+               &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
+               &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
 
                if ($oldnet->{bridge}) {
                    PVE::Network::tap_unplug($veth);
                    foreach (qw(bridge tag firewall)) {
                        delete $oldnet->{$_};
                    }
-                   $conf->{$opt} = print_lxc_network($oldnet);
-                   write_config($vmid, $conf);
+                   $conf->{$opt} = PVE::LXC::Config->print_lxc_network($oldnet);
+                   PVE::LXC::Config->write_config($vmid, $conf);
                }
 
-               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
-               foreach (qw(bridge tag firewall)) {
+               PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
+               # This includes the rate:
+               foreach (qw(bridge tag firewall rate)) {
                    $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
                }
-               $conf->{$opt} = print_lxc_network($oldnet);
-               write_config($vmid, $conf);
+           } elsif (&$safe_string_ne($oldnet->{rate}, $newnet->{rate})) {
+               # Rate can be applied on its own but any change above needs to
+               # include the rate in tap_plug since OVS resets everything.
+               PVE::Network::tap_rate_limit($veth, $newnet->{rate});
+               $oldnet->{rate} = $newnet->{rate}
+           }
+           $conf->{$opt} = PVE::LXC::Config->print_lxc_network($oldnet);
+           PVE::LXC::Config->write_config($vmid, $conf);
        }
     } else {
        hotplug_net($vmid, $conf, $opt, $newnet, $netid);
@@ -1633,7 +632,7 @@ sub hotplug_net {
     my $eth = $newnet->{name};
 
     PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
-    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
+    PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate});
 
     # attach peer in container
     my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
@@ -1647,9 +646,9 @@ sub hotplug_net {
     foreach (qw(bridge tag firewall hwaddr name)) {
        $done->{$_} = $newnet->{$_} if $newnet->{$_};
     }
-    $conf->{$opt} = print_lxc_network($done);
+    $conf->{$opt} = PVE::LXC::Config->print_lxc_network($done);
 
-    write_config($vmid, $conf);
+    PVE::LXC::Config->write_config($vmid, $conf);
 }
 
 sub update_ipconfig {
@@ -1657,7 +656,7 @@ sub update_ipconfig {
 
     my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
 
-    my $optdata = parse_lxc_network($conf->{$opt});
+    my $optdata = PVE::LXC::Config->parse_lxc_network($conf->{$opt});
     my $deleted = [];
     my $added = [];
     my $nscmd = sub {
@@ -1752,8 +751,8 @@ sub update_ipconfig {
                delete $optdata->{$property};
            }
        }
-       $conf->{$opt} = print_lxc_network($optdata);
-       write_config($vmid, $conf);
+       $conf->{$opt} = PVE::LXC::Config->print_lxc_network($optdata);
+       PVE::LXC::Config->write_config($vmid, $conf);
        $lxc_setup->setup_network($conf);
     };
 
@@ -1762,142 +761,6 @@ sub update_ipconfig {
 
 }
 
-# Internal snapshots
-
-# NOTE: Snapshot create/delete involves several non-atomic
-# actions, and can take a long time.
-# So we try to avoid locking the file and use the 'lock' variable
-# inside the config file instead.
-
-my $snapshot_copy_config = sub {
-    my ($source, $dest) = @_;
-
-    foreach my $k (keys %$source) {
-       next if $k eq 'snapshots';
-       next if $k eq 'snapstate';
-       next if $k eq 'snaptime';
-       next if $k eq 'vmstate';
-       next if $k eq 'lock';
-       next if $k eq 'digest';
-       next if $k eq 'description';
-       next if $k =~ m/^unused\d+$/;
-
-       $dest->{$k} = $source->{$k};
-    }
-};
-
-my $snapshot_apply_config = sub {
-    my ($conf, $snap) = @_;
-
-    # copy snapshot list
-    my $newconf = {
-       snapshots => $conf->{snapshots},
-    };
-
-    # keep description and list of unused disks
-    foreach my $k (keys %$conf) {
-       next if !($k =~ m/^unused\d+$/ || $k eq 'description');
-       $newconf->{$k} = $conf->{$k};
-    }
-
-    &$snapshot_copy_config($snap, $newconf);
-
-    return $newconf;
-};
-
-sub snapshot_save_vmstate {
-    die "implement me - snapshot_save_vmstate\n";
-}
-
-sub snapshot_prepare {
-    my ($vmid, $snapname, $save_vmstate, $comment) = @_;
-
-    my $snap;
-
-    my $updatefn =  sub {
-
-       my $conf = load_config($vmid);
-
-       die "you can't take a snapshot if it's a template\n"
-           if is_template($conf);
-
-       check_lock($conf);
-
-       $conf->{lock} = 'snapshot';
-
-       die "snapshot name '$snapname' already used\n"
-           if defined($conf->{snapshots}->{$snapname});
-
-       my $storecfg = PVE::Storage::config();
-       die "snapshot feature is not available\n"
-           if !has_feature('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
-
-       $snap = $conf->{snapshots}->{$snapname} = {};
-
-       if ($save_vmstate && check_running($vmid)) {
-           snapshot_save_vmstate($vmid, $conf, $snapname, $storecfg);
-       }
-
-       &$snapshot_copy_config($conf, $snap);
-
-       $snap->{snapstate} = "prepare";
-       $snap->{snaptime} = time();
-       $snap->{description} = $comment if $comment;
-
-       write_config($vmid, $conf);
-    };
-
-    lock_config($vmid, $updatefn);
-
-    return $snap;
-}
-
-sub snapshot_commit {
-    my ($vmid, $snapname) = @_;
-
-    my $updatefn = sub {
-
-       my $conf = load_config($vmid);
-
-       die "missing snapshot lock\n"
-           if !($conf->{lock} && $conf->{lock} eq 'snapshot');
-
-       my $snap = $conf->{snapshots}->{$snapname};
-       die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
-       die "wrong snapshot state\n"
-           if !($snap->{snapstate} && $snap->{snapstate} eq "prepare");
-
-       delete $snap->{snapstate};
-       delete $conf->{lock};
-
-       my $newconf = &$snapshot_apply_config($conf, $snap);
-
-       $newconf->{parent} = $snapname;
-
-       write_config($vmid, $newconf);
-    };
-
-    lock_config($vmid, $updatefn);
-}
-
-sub has_feature {
-    my ($feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
-    
-    my $err;
-
-    foreach_mountpoint($conf, sub {
-       my ($ms, $mountpoint) = @_;
-
-       return if $err; # skip further test
-       return if $backup_only && $ms ne 'rootfs' && !$mountpoint->{backup};
-       
-       $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname, $running);
-    });
-
-    return $err ? 0 : 1;
-}
-
 my $enter_namespace = sub {
     my ($vmid, $pid, $which, $type) = @_;
     sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
@@ -1967,250 +830,12 @@ sub sync_container_namespace {
     die "failed to sync container namespace\n" if $? != 0;
 }
 
-sub check_freeze_needed {
-    my ($vmid, $config, $save_vmstate) = @_;
-
-    my $ret = check_running($vmid);
-    return ($ret, $ret);
-}
-
-sub snapshot_create {
-    my ($vmid, $snapname, $save_vmstate, $comment) = @_;
-
-    my $snap = snapshot_prepare($vmid, $snapname, $save_vmstate, $comment);
-
-    $save_vmstate = 0 if !$snap->{vmstate};
-
-    my $conf = load_config($vmid);
-
-    my ($running, $freezefs) = check_freeze_needed($vmid, $conf, $snap->{vmstate});
-
-    my $drivehash = {};
-
-    eval {
-       if ($freezefs) {
-           PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
-           sync_container_namespace($vmid);
-       }
-
-       my $storecfg = PVE::Storage::config();
-       foreach_mountpoint($conf, sub {
-           my ($ms, $mountpoint) = @_;
-
-           return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup};
-           PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
-           $drivehash->{$ms} = 1;
-       });
-    };
-    my $err = $@;
-    
-    if ($running) {
-       if ($freezefs) {
-           eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
-           warn $@ if $@;
-       }
-    }
-    
-    if ($err) {
-       warn "snapshot create failed: starting cleanup\n";
-       eval { snapshot_delete($vmid, $snapname, 1, $drivehash); };
-       warn "$@" if $@;
-       die "$err\n";
-    }
-
-    snapshot_commit($vmid, $snapname);
-}
-
-# Note: $drivehash is only set when called from snapshot_create.
-sub snapshot_delete {
-    my ($vmid, $snapname, $force, $drivehash) = @_;
-
-    my $prepare = 1;
-
-    my $snap;
-    my $unused = [];
-
-    my $unlink_parent = sub {
-       my ($confref, $new_parent) = @_;
-
-       if ($confref->{parent} && $confref->{parent} eq $snapname) {
-           if ($new_parent) {
-               $confref->{parent} = $new_parent;
-           } else {
-               delete $confref->{parent};
-           }
-       }
-    };
-
-    my $updatefn =  sub {
-       my ($remove_drive) = @_;
-
-       my $conf = load_config($vmid);
-
-       if (!$drivehash) {
-           check_lock($conf);
-           die "you can't delete a snapshot if vm is a template\n"
-               if is_template($conf);
-       }
-
-       $snap = $conf->{snapshots}->{$snapname};
-
-       die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
-       # remove parent refs
-       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) {
-           if ($remove_drive eq 'vmstate') {
-               die "implement me - saving vmstate\n";
-           } else {
-               my $value = $snap->{$remove_drive};
-               my $mountpoint = $remove_drive eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
-               delete $snap->{$remove_drive};
-               add_unused_volume($snap, $mountpoint->{volume})
-                   if (!is_volume_in_use($snap, $mountpoint->{volume}));
-           }
-       }
-
-       if ($prepare) {
-           $snap->{snapstate} = 'delete';
-       } else {
-           delete $conf->{snapshots}->{$snapname};
-           delete $conf->{lock} if $drivehash;
-           foreach my $volid (@$unused) {
-               add_unused_volume($conf, $volid)
-                   if (!is_volume_in_use($conf, $volid));
-           }
-       }
-
-       write_config($vmid, $conf);
-    };
-
-    lock_config($vmid, $updatefn);
-
-    # now remove vmstate file
-    # never set for LXC!
-    my $storecfg = PVE::Storage::config();
-
-    if ($snap->{vmstate}) {
-       die "implement me - saving vmstate\n";
-    };
-
-    # now remove all volume snapshots
-    foreach_mountpoint($snap, sub {
-       my ($ms, $mountpoint) = @_;
-
-       return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup};
-       if (!$drivehash || $drivehash->{$ms}) {
-           eval { PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname); };
-           if (my $err = $@) {
-               die $err if !$force;
-               warn $err;
-           }
-       }
-
-       # save changes (remove mp from snapshot)
-       lock_config($vmid, $updatefn, $ms) if !$force;
-       push @$unused, $mountpoint->{volume};
-    });
-
-    # now cleanup config
-    $prepare = 0;
-    lock_config($vmid, $updatefn);
-}
-
-sub snapshot_rollback {
-    my ($vmid, $snapname) = @_;
-
-    my $prepare = 1;
-
-    my $storecfg = PVE::Storage::config();
-
-    my $conf = load_config($vmid);
-
-    my $get_snapshot_config = sub {
-
-       die "you can't rollback if vm is a template\n" if is_template($conf);
-
-       my $res = $conf->{snapshots}->{$snapname};
-
-       die "snapshot '$snapname' does not exist\n" if !defined($res);
-
-       return $res;
-    };
-
-    my $snap = &$get_snapshot_config();
-
-    foreach_mountpoint($snap, sub {
-       my ($ms, $mountpoint) = @_;
-
-       PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
-    });
-
-    my $updatefn = sub {
-
-       $conf = load_config($vmid);
-
-       $snap = &$get_snapshot_config();
-
-       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
-           if $snap->{snapstate};
-
-       if ($prepare) {
-           check_lock($conf);
-           PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
-               if check_running($vmid);
-       }
-
-       die "unable to rollback vm $vmid: vm is running\n"
-           if check_running($vmid);
-
-       if ($prepare) {
-           $conf->{lock} = 'rollback';
-       } else {
-           die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
-           delete $conf->{lock};
-       }
-
-       my $forcemachine;
-
-       if (!$prepare) {
-           # copy snapshot config to current config
-           $conf = &$snapshot_apply_config($conf, $snap);
-           $conf->{parent} = $snapname;
-       }
-
-       write_config($vmid, $conf);
-
-       if (!$prepare && $snap->{vmstate}) {
-           die "implement me - save vmstate\n";
-       }
-    };
-
-    lock_config($vmid, $updatefn);
-
-    foreach_mountpoint($snap, sub {
-       my ($ms, $mountpoint) = @_;
-
-       PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
-    });
-
-    $prepare = 0;
-    lock_config($vmid, $updatefn);
-}
-
 sub template_create {
     my ($vmid, $conf) = @_;
 
     my $storecfg = PVE::Storage::config();
 
-    my $rootinfo = parse_ct_rootfs($conf->{rootfs});
+    my $rootinfo = PVE::LXC::Config->parse_ct_rootfs($conf->{rootfs});
     my $volid = $rootinfo->{volume};
 
     die "Template feature is not available for '$volid'\n"
@@ -2220,53 +845,9 @@ sub template_create {
 
     my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
     $rootinfo->{volume} = $template_volid;
-    $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
-
-    write_config($vmid, $conf);
-}
-
-sub is_template {
-    my ($conf) = @_;
-
-    return 1 if defined $conf->{template} && $conf->{template} == 1;
-}
-
-sub mountpoint_names {
-    my ($reverse) = @_;
+    $conf->{rootfs} = PVE::LXC::Config->print_ct_mountpoint($rootinfo, 1);
 
-    my @names = ('rootfs');
-
-    for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
-       push @names, "mp$i";
-    }
-
-    return $reverse ? reverse @names : @names;
-}
-
-
-sub foreach_mountpoint_full {
-    my ($conf, $reverse, $func) = @_;
-
-    foreach my $key (mountpoint_names($reverse)) {
-       my $value = $conf->{$key};
-       next if !defined($value);
-       my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
-       next if !defined($mountpoint);
-
-       &$func($key, $mountpoint);
-    }
-}
-
-sub foreach_mountpoint {
-    my ($conf, $func) = @_;
-
-    foreach_mountpoint_full($conf, 0, $func);
-}
-
-sub foreach_mountpoint_reverse {
-    my ($conf, $func) = @_;
-
-    foreach_mountpoint_full($conf, 1, $func);
+    PVE::LXC::Config->write_config($vmid, $conf);
 }
 
 sub check_ct_modify_config_perm {
@@ -2281,8 +862,8 @@ sub check_ct_modify_config_perm {
        } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
            return if $delete;
-           my $data = $opt eq 'rootfs' ? parse_ct_rootfs($newconf->{$opt})
-                                       : parse_ct_mountpoint($newconf->{$opt});
+           my $data = $opt eq 'rootfs' ? PVE::LXC::Config->parse_ct_rootfs($newconf->{$opt})
+                                       : PVE::LXC::Config->parse_ct_mountpoint($newconf->{$opt});
            raise_perm_exc("mountpoint type $data->{type}") if $data->{type} ne 'volume';
        } elsif ($opt eq 'memory' || $opt eq 'swap') {
            $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
@@ -2308,9 +889,9 @@ sub umount_all {
     my ($vmid, $storage_cfg, $conf, $noerr) = @_;
 
     my $rootdir = "/var/lib/lxc/$vmid/rootfs";
-    my $volid_list = get_vm_volumes($conf);
+    my $volid_list = PVE::LXC::Config->get_vm_volumes($conf);
 
-    foreach_mountpoint_reverse($conf, sub {
+    PVE::LXC::Config->foreach_mountpoint_reverse($conf, sub {
        my ($ms, $mountpoint) = @_;
 
        my $volid = $mountpoint->{volume};
@@ -2342,11 +923,11 @@ sub mount_all {
     my $rootdir = "/var/lib/lxc/$vmid/rootfs";
     File::Path::make_path($rootdir);
 
-    my $volid_list = get_vm_volumes($conf);
+    my $volid_list = PVE::LXC::Config->get_vm_volumes($conf);
     PVE::Storage::activate_volumes($storage_cfg, $volid_list);
 
     eval {
-       foreach_mountpoint($conf, sub {
+       PVE::LXC::Config->foreach_mountpoint($conf, sub {
            my ($ms, $mountpoint) = @_;
 
            mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
@@ -2461,12 +1042,13 @@ sub mountpoint_mount {
     die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
 
     my $optstring = '';
-    if (defined($mountpoint->{acl})) {
-       $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl');
+    my $acl = $mountpoint->{acl};
+    if (defined($acl)) {
+       $optstring .= ($acl ? 'acl' : 'noacl');
     }
     my $readonly = $mountpoint->{ro};
 
-    my @extra_opts = ('-o', $optstring);
+    my @extra_opts = ('-o', $optstring) if $optstring;
 
     if ($storage) {
 
@@ -2489,12 +1071,23 @@ sub mountpoint_mount {
                        die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
                    }
                } else {
+                   if (defined($acl) && $scfg->{type} eq 'zfspool') {
+                       my $acltype = ($acl ? 'acltype=posixacl' : 'acltype=noacl');
+                       my (undef, $name) = PVE::Storage::parse_volname($storage_cfg, $volid);
+                       $name .= "\@$snapname" if defined($snapname);
+                       PVE::Tools::run_command(['zfs', 'set', $acltype, "$scfg->{pool}/$name"]);
+                   }
                    bindmount($path, $mount_path, $readonly, @extra_opts);
                    warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
                }
            }
-           return wantarray ? ($path, 0, $mounted_dev) : $path;
+           return wantarray ? ($path, 0, undef) : $path;
        } elsif ($format eq 'raw' || $format eq 'iso') {
+           # NOTE: 'mount' performs canonicalization without the '-c' switch, which for
+           # device-mapper devices is special-cased to use the /dev/mapper symlinks.
+           # Our autodev hook expects the /dev/dm-* device currently
+           # and will create the /dev/mapper symlink accordingly
+           ($path) = (Cwd::realpath($path) =~ /^(.*)$/s); # realpath() taints
            my $domount = sub {
                my ($path) = @_;
                if ($mount_path) {
@@ -2527,9 +1120,12 @@ sub mountpoint_mount {
            die "unsupported image format '$format'\n";
        }
     } elsif ($type eq 'device') {
-                       push @extra_opts, '-o', 'ro' if $readonly;
+       push @extra_opts, '-o', 'ro' if $readonly;
+       push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0' if $quota;
+       # See the NOTE above about devicemapper canonicalization
+       my ($devpath) = (Cwd::realpath($volid) =~ /^(.*)$/s); # realpath() taints
        PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
-       return wantarray ? ($volid, 0, $volid) : $volid;
+       return wantarray ? ($volid, 0, $devpath) : $volid;
     } elsif ($type eq 'bind') {
        die "directory '$volid' does not exist\n" if ! -d $volid;
        &$check_mount_path($volid);
@@ -2541,29 +1137,6 @@ sub mountpoint_mount {
     die "unsupported storage";
 }
 
-sub get_vm_volumes {
-    my ($conf, $excludes) = @_;
-
-    my $vollist = [];
-
-    foreach_mountpoint($conf, sub {
-       my ($ms, $mountpoint) = @_;
-
-       return if $excludes && $ms eq $excludes;
-
-       my $volid = $mountpoint->{volume};
-
-        return if !$volid || $mountpoint->{type} ne 'volume';
-
-        my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
-        return if !$sid;
-
-        push @$vollist, $volid;
-    });
-
-    return $vollist;
-}
-
 sub mkfs {
     my ($dev, $rootuid, $rootgid) = @_;
 
@@ -2615,7 +1188,7 @@ sub create_disks {
        my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
        my $chown_vollist = [];
 
-       foreach_mountpoint($settings, sub {
+       PVE::LXC::Config->foreach_mountpoint($settings, sub {
            my ($ms, $mountpoint) = @_;
 
            my $volid = $mountpoint->{volume};
@@ -2662,10 +1235,10 @@ sub create_disks {
                push @$vollist, $volid;
                $mountpoint->{volume} = $volid;
                $mountpoint->{size} = $size_kb * 1024;
-               $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+               $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
            } else {
                 # use specified/existing volid/dir/device
-                $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+                $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
            }
        });
 
@@ -2785,30 +1358,5 @@ sub userns_command {
     return [];
 }
 
-sub set_lock {
-    my ($vmid, $lock) = @_;
-    my $conf;
-    lock_config($vmid, sub {
-       $conf = load_config($vmid);
-       check_lock($conf);
-       $conf->{lock} = $lock;
-       write_config($vmid, $conf);
-    });
-    return $conf;
-}
-
-sub remove_lock {
-    my ($vmid, $lock) = @_;
-    lock_config($vmid, sub {
-       my $conf = load_config($vmid);
-       if (!$conf->{lock}) {
-           die "no lock found trying to remove lock '$lock'\n";
-       } elsif (defined($lock) && $conf->{lock} ne $lock) {
-           die "found lock '$conf->{lock}' trying to remove lock '$lock'\n";
-       }
-       delete $conf->{lock};
-       write_config($vmid, $conf);
-    });
-}
 
 1;