my $lockdir = "/run/lock/lxc";
mkdir $lockdir;
mkdir "/etc/pve/nodes/$nodename/lxc";
-my $MAX_MOUNT_POINTS = 10;
+my $MAX_MOUNT_POINTS = 256;
my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
# BEGIN implemented abstract methods from PVE::AbstractConfig
sub __snapshot_rollback_vm_stop {
my ($class, $vmid) = @_;
- PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
+ PVE::LXC::vm_stop($vmid, 1)
if $class->__snapshot_check_running($vmid);
}
sub __snapshot_rollback_vm_start {
- my ($class, $vmid, $vmstate, $forcemachine);
+ my ($class, $vmid, $vmstate, $data);
die "implement me - save vmstate\n";
}
maxLength => 40,
});
+my $features_desc = {
+ mount => {
+ optional => 1,
+ type => 'string',
+ description => "Allow mounting file systems of specific types."
+ ." This should be a list of file system types as used with the mount command."
+ ." Note that this can have negative effects on the container's security."
+ ." With access to a loop device, mounting a file can circumvent the mknod"
+ ." permission of the devices cgroup, mounting an NFS file system can"
+ ." block the host's I/O completely and prevent it from rebooting, etc.",
+ format_description => 'fstype;fstype;...',
+ pattern => qr/[a-zA-Z0-9; ]+/,
+ },
+ nesting => {
+ optional => 1,
+ type => 'boolean',
+ default => 0,
+ description => "Allow nesting."
+ ." Best used with unprivileged containers with additional id mapping."
+ ." Note that this will expose procfs and sysfs contents of the host"
+ ." to the guest.",
+ },
+ keyctl => {
+ optional => 1,
+ type => 'boolean',
+ default => 0,
+ description => "For unprivileged containers only: Allow the use of the keyctl() system call."
+ ." This is required to use docker inside a container."
+ ." By default unprivileged containers will see this system call as non-existent."
+ ." This is mostly a workaround for systemd-networkd, as it will treat it as a fatal"
+ ." error when some keyctl() operations are denied by the kernel due to lacking permissions."
+ ." Essentially, you can choose between running systemd-networkd or docker.",
+ },
+};
+
my $confdesc = {
lock => {
optional => 1,
type => 'string',
description => "Lock/unlock the VM.",
- enum => [qw(migrate backup snapshot rollback)],
+ enum => [qw(backup disk migrate mounted rollback snapshot snapshot-delete)],
},
onboot => {
optional => 1,
arch => {
optional => 1,
type => 'string',
- enum => ['amd64', 'i386'],
+ enum => ['amd64', 'i386', 'arm64', 'armhf'],
description => "OS architecture type.",
default => 'amd64',
},
description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
default => 0,
},
+ features => {
+ optional => 1,
+ type => 'string',
+ format => $features_desc,
+ description => "Allow containers access to advanced features.",
+ },
};
my $valid_lxc_conf_keys = {
'lxc.apparmor.profile' => 1,
'lxc.apparmor.allow_incomplete' => 1,
+ 'lxc.apparmor.allow_nesting' => 1,
+ 'lxc.apparmor.raw' => 1,
'lxc.selinux.context' => 1,
'lxc.include' => 1,
'lxc.arch' => 1,
ip => {
type => 'string',
format => 'pve-ipv4-config',
- format_description => 'IPv4Format/CIDR',
+ format_description => '(IPv4/CIDR|dhcp|manual)',
description => 'IPv4 address in CIDR format.',
optional => 1,
},
ip6 => {
type => 'string',
format => 'pve-ipv6-config',
- format_description => 'IPv6Format/CIDR',
+ format_description => '(IPv6/CIDR|auto|dhcp|manual)',
description => 'IPv6 address in CIDR format.',
optional => 1,
},
}
} elsif ($opt eq 'unprivileged') {
die "unable to delete read-only option: '$opt'\n";
+ } elsif ($opt eq 'features') {
+ next if $hotplug_error->($opt);
+ delete $conf->{$opt};
} else {
die "implement me (delete: $opt)"
}
if !$storage_config->{content}->{rootdir};
};
+ my $rescan_volume = sub {
+ my ($mp) = @_;
+ eval {
+ $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
+ if !defined($mp->{size});
+ };
+ warn "Could not rescan volume size - $@\n" if $@;
+ };
+
foreach my $opt (keys %$param) {
my $value = $param->{$opt};
my $check_protection_msg = "can't update CT $vmid drive '$opt'";
$conf->{$opt} = $value;
PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
} elsif ($opt eq 'description') {
- $conf->{$opt} = PVE::Tools::encode_text($value);
+ $conf->{$opt} = $value;
} elsif ($opt =~ m/^net(\d+)$/) {
my $netid = $1;
my $net = PVE::LXC::Config->parse_lxc_network($value);
if ($mp->{type} eq 'volume') {
&$check_content_type($mp);
$used_volids->{$mp->{volume}} = 1;
+ &$rescan_volume($mp);
+ $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
+ } else {
+ $conf->{$opt} = $value;
}
- $conf->{$opt} = $value;
if (defined($old)) {
my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
if ($mp->{type} eq 'volume') {
next if $hotplug_error->($opt);
PVE::LXC::Config->check_protection($conf, $check_protection_msg);
my $old = $conf->{$opt};
- $conf->{$opt} = $value;
my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
if ($mp->{type} eq 'volume') {
&$check_content_type($mp);
$used_volids->{$mp->{volume}} = 1;
+ &$rescan_volume($mp);
+ $conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
+ } else {
+ $conf->{$opt} = $value;
}
if (defined($old)) {
my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
} elsif ($opt eq 'ostype') {
next if $hotplug_error->($opt);
$conf->{$opt} = $value;
+ } elsif ($opt eq 'features') {
+ next if $hotplug_error->($opt);
+ $conf->{$opt} = $value;
} else {
die "implement me: $opt";
}
return $res;
}
+sub parse_features {
+ my ($class, $data) = @_;
+ return {} if !$data;
+ return PVE::JSONSchema::parse_property_string($features_desc, $data);
+}
+
sub option_exists {
my ($class, $name) = @_;
return 'volume';
}
-sub is_volume_in_use {
- my ($class, $config, $volid, $include_snapshots) = @_;
+my $is_volume_in_use = sub {
+ my ($class, $config, $volid) = @_;
my $used = 0;
$class->foreach_mountpoint($config, sub {
$used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
});
- my $snapshots = $config->{snapshots};
- if ($include_snapshots && $snapshots) {
+ return $used;
+};
+
+sub is_volume_in_use_by_snapshots {
+ my ($class, $config, $volid) = @_;
+
+ if (my $snapshots = $config->{snapshots}) {
foreach my $snap (keys %$snapshots) {
- $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
+ return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
}
}
- return $used;
+ return 0;
+};
+
+sub is_volume_in_use {
+ my ($class, $config, $volid, $include_snapshots) = @_;
+ return 1 if $is_volume_in_use->($class, $config, $volid);
+ return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
+ return 0;
}
sub has_dev_console {
sub foreach_mountpoint_full {
my ($class, $conf, $reverse, $func, @param) = @_;
- foreach my $key ($class->mountpoint_names($reverse)) {
+ my $mps = [ grep { defined($conf->{$_}) } $class->mountpoint_names($reverse) ];
+ foreach my $key (@$mps) {
my $value = $conf->{$key};
- next if !defined($value);
my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
next if !defined($mountpoint);
return if !$volid;
my $mptype = $mountpoint->{type};
- die "unable to replicate mountpoint type '$mptype'\n"
- if $mptype ne 'volume';
+ my $replicate = $mountpoint->{replicate} // 1;
+
+ if ($mptype ne 'volume') {
+ # skip bindmounts if replicate = 0 even for cleanup,
+ # since bind mounts could not have been replicated ever
+ return if !$replicate;
+ die "unable to replicate mountpoint type '$mptype'\n";
+ }
my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, $noerr);
return if !$storeid;
die "unable to replicate volume '$volid', type '$vtype'\n" if $vtype ne 'images';
- return if !$cleanup && defined($mountpoint->{replicate}) && !$mountpoint->{replicate};
+ return if !$cleanup && !$replicate;
if (!PVE::Storage::volume_has_feature($storecfg, 'replicate', $volid)) {
return if $cleanup || $noerr;