use strict;
use warnings;
+
use POSIX qw(EINTR);
+use Socket;
+
use File::Path;
use File::Spec;
use Cwd qw();
-use Fcntl ':flock';
+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);
+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;
my $cpuinfo= PVE::ProcFSTools::read_cpuinfo();
-our $COMMON_TAR_FLAGS = [ '--totals', '--sparse', '--numeric-owner', '--acls',
+our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
'--xattrs',
'--xattrs-include=user.*',
'--xattrs-include=security.capability',
volume => {
type => 'string',
default_key => 1,
+ format => 'pve-lxc-mp-string',
format_description => 'volume',
description => 'Volume, device or directory to mount into the container.',
},
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', {
ostype => {
optional => 1,
type => 'string',
- enum => ['debian', 'ubuntu', 'centos', 'archlinux'],
- description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
+ 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,
cpulimit => {
optional => 1,
type => 'number',
- description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
+ 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 weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
+ 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,
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 or nameserver.",
+ 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 or nameserver.",
+ 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 => {
protection => {
optional => 1,
type => 'boolean',
- description => "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
+ description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
default => 0,
},
unprivileged => {
'lxc.mount' => 1,
'lxc.mount.entry' => 1,
'lxc.mount.auto' => 1,
- 'lxc.rootfs' => 1,
+ 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
'lxc.rootfs.mount' => 1,
- 'lxc.rootfs.options' => 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.start.order' => 1,
'lxc.group' => 1,
'lxc.environment' => 1,
- 'lxc.' => 1,
- 'lxc.' => 1,
- 'lxc.' => 1,
- 'lxc.' => 1,
};
my $netconf_desc = {
format_description => 'VlanNo',
minimum => '2',
maximum => '4094',
- description => "VLAN tag foro this interface.",
+ 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-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.',
- optional => 1,
},
};
PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
my ($filename, $conf) = @_;
delete $conf->{snapstate}; # just to be sure
+ my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
+ my $used_volids = {};
+ foreach my $vid (@$volidlist) {
+ $used_volids->{$vid} = 1;
+ }
+
+ # remove 'unusedX' settings if the volume is still used
+ foreach my $key (keys %$conf) {
+ my $value = $conf->{$key};
+ if ($key =~ m/^unused/ && $used_volids->{$value}) {
+ delete $conf->{$key};
+ }
+ }
my $generate_raw_config = sub {
my ($conf) = @_;
if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
my $key = $1;
my $value = $3;
- if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
+ 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";
}
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 exists\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 succeeds for the same process.
-
-my $lock_handles = {};
-my $lockdir = "/run/lock/lxc";
-
-sub lock_filename {
- my ($vmid) = @_;
-
- return "$lockdir/pve-config-${vmid}.lock";
-}
-
-sub lock_aquire {
- my ($vmid, $timeout) = @_;
-
- $timeout = 10 if !$timeout;
- my $mode = LOCK_EX;
-
- my $filename = lock_filename($vmid);
-
- mkdir $lockdir if !-d $lockdir;
-
- my $lock_func = sub {
- if (!$lock_handles->{$$}->{$filename}) {
- my $fh = new IO::File(">>$filename") ||
- die "can't open file - $!\n";
- $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
- }
-
- if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
- print STDERR "trying to aquire lock...";
- my $success;
- while(1) {
- $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
- # try again on EINTR (see bug #273)
- if ($success || ($! != EINTR)) {
- last;
- }
- }
- if (!$success) {
- print STDERR " failed\n";
- die "can't aquire lock - $!\n";
- }
-
- print STDERR " OK\n";
- }
-
- $lock_handles->{$$}->{$filename}->{refcount}++;
- };
-
- eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
- my $err = $@;
- if ($err) {
- die "can't lock file '$filename' - $err";
- }
-}
-
-sub lock_release {
- my ($vmid) = @_;
-
- my $filename = lock_filename($vmid);
-
- if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
- my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
- if ($refcount <= 0) {
- $lock_handles->{$$}->{$filename} = undef;
- close ($fh);
- }
- }
-}
-
-sub lock_container {
- my ($vmid, $timeout, $code, @param) = @_;
-
- my $res;
-
- lock_aquire($vmid, $timeout);
- eval { $res = &$code(@param) };
- my $err = $@;
- lock_release($vmid);
-
- die $err if $err;
-
- return $res;
+ unlink PVE::LXC::Config->config_file($vmid, $nodename);
}
sub option_exists {
return $prop;
}
-sub json_config_properties_no_rootfs {
- my $prop = shift;
-
- foreach my $opt (keys %$confdesc) {
- next if $prop->{$opt};
- next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
- $prop->{$opt} = $confdesc->{$opt};
- }
-
- return $prop;
-}
-
# container status helpers
sub list_active_containers {
$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";
$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};
$d->{disk} = 0;
# use 4GB by default ??
if (my $rootfs = $conf->{rootfs}) {
- my $rootinfo = parse_ct_mountpoint($rootfs);
+ my $rootinfo = parse_ct_rootfs($rootfs);
$d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
} else {
$d->{maxdisk} = 4*1024*1024*1024;
$d->{diskread} = 0;
$d->{diskwrite} = 0;
- $d->{template} = is_template($conf);
+ $d->{template} = PVE::LXC::Config->is_template($conf);
}
foreach my $vmid (keys %$list) {
return $list;
}
-sub parse_ct_mountpoint {
- my ($data, $noerr) = @_;
+my $parse_ct_mountpoint_full = sub {
+ my ($desc, $data, $noerr) = @_;
$data //= '';
my $res;
- eval { $res = PVE::JSONSchema::parse_property_string($mp_desc, $data) };
+ eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
if ($@) {
return undef if $noerr;
die $@;
$res->{size} = $size;
}
+ $res->{type} = PVE::LXC::Config->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 = $nomp ? ['mp'] : [];
+ my $skip = [ 'type' ];
+ push @$skip, 'mp' if $nomp;
return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
}
my @args = split(/\0/, $cmdline);
- # serach for lxc-console -n <vmid>
+ # search for lxc-console -n <vmid>
return if scalar(@args) != 3;
return if $args[1] ne '-n';
return if $args[2] !~ m/^\d+$/;
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 $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
- if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
- $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
+ if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) {
+ my $inc ="/usr/share/lxc/config/$ostype.common.conf";
+ $inc ="/usr/share/lxc/config/common.conf" if !-f $inc;
+ $raw .= "lxc.include = $inc\n";
if ($unprivileged || $custom_idmap) {
- $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
+ $inc = "/usr/share/lxc/config/$ostype.userns.conf";
+ $inc = "/usr/share/lxc/config/userns.conf" if !-f $inc;
+ $raw .= "lxc.include = $inc\n"
}
} else {
- die "implement me";
+ die "implement me (ostype $ostype)";
}
+ # WARNING: DO NOT REMOVE this without making sure that loop device nodes
+ # cannot be exposed to the container with r/w access (cgroup perms).
+ # When this is enabled mounts will still remain in the monitor's namespace
+ # after the container unmounted them and thus will not detach from their
+ # files while the container is running!
+ $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)) {
+ 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);
$raw .= "lxc.tty = $ttycount\n";
- # some init scripts expects a linux terminal (turnkey).
+ # some init scripts expect a linux terminal (turnkey).
$raw .= "lxc.environment = TERM=linux\n";
my $utsname = $conf->{hostname} || "CT$vmid";
my $shares = $conf->{cpuunits} || 1024;
$raw .= "lxc.cgroup.cpu.shares = $shares\n";
- my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
- $mountpoint->{mp} = '/';
+ my $mountpoint = parse_ct_rootfs($conf->{rootfs});
$raw .= "lxc.rootfs = $dir/rootfs\n";
return join(' ', @list);
}
-sub add_unused_volume {
- my ($config, $volid) = @_;
-
- # skip bind mounts and block devices
- return if $volid =~ m|^/|;
-
- 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 "To many unused volume - please delete them first.\n" if !$key;
-
- $config->{$key} = $volid;
-
- return $key;
-}
-
sub update_pct_config {
my ($vmid, $conf, $running, $param, $delete) = @_;
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') {
} elsif ($opt =~ m/^mp(\d+)$/) {
next if $hotplug_error->($opt);
check_protection($conf, "can't remove CT $vmid drive '$opt'");
- my $mountpoint = parse_ct_mountpoint($conf->{$opt});
- add_unused_volume($conf, $mountpoint->{volume});
+ my $mp = parse_ct_mountpoint($conf->{$opt});
delete $conf->{$opt};
+ if ($mp->{type} eq 'volume') {
+ PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
+ }
} elsif ($opt eq 'unprivileged') {
die "unable to delete read-only option: '$opt'\n";
} else {
- die "implement me"
+ die "implement me (delete: $opt)"
}
- write_config($vmid, $conf) if $running;
+ PVE::LXC::Config->write_config($vmid, $conf) if $running;
}
}
my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
if (defined($wanted_memory) || defined($wanted_swap)) {
- $wanted_memory //= ($conf->{memory} || 512);
- $wanted_swap //= ($conf->{swap} || 0);
+ 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) {
- 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));
+ 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;
+ PVE::LXC::Config->write_config($vmid, $conf) if $running;
}
+ my $used_volids = {};
+
foreach my $opt (keys %$param) {
my $value = $param->{$opt};
if ($opt eq 'hostname') {
} 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') {
+ PVE::LXC::Config->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'");
- die "implement me: $opt";
+ my $old = $conf->{$opt};
+ $conf->{$opt} = $value;
+ if (defined($old)) {
+ my $mp = parse_ct_rootfs($old);
+ if ($mp->{type} eq 'volume') {
+ PVE::LXC::Config->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;
+ PVE::LXC::Config->write_config($vmid, $conf) if $running;
}
+ # 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 PVE::LXC::Config->is_volume_in_use($conf, $volume, 1);
delete_mountpoint_volume($storage_cfg, $vmid, $volume);
}
}
}
}
-sub has_dev_console {
- my ($conf) = @_;
-
- return !(defined($conf->{console}) && !$conf->{console});
-}
-
sub get_tty_count {
my ($conf) = @_;
sub delete_mountpoint_volume {
my ($storage_cfg, $vmid, $volume) = @_;
- # skip bind mounts and block devices
- if ($volume =~ m|^/|) {
- return;
- }
+ 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;
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});
});
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);
}
};
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);
delete $oldnet->{$_};
}
$conf->{$opt} = print_lxc_network($oldnet);
- write_config($vmid, $conf);
+ PVE::LXC::Config->write_config($vmid, $conf);
}
- PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+ PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
foreach (qw(bridge tag firewall)) {
$oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
}
$conf->{$opt} = print_lxc_network($oldnet);
- write_config($vmid, $conf);
+ PVE::LXC::Config->write_config($vmid, $conf);
}
} else {
hotplug_net($vmid, $conf, $opt, $newnet, $netid);
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});
+ PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks});
# attach peer in container
my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
}
$conf->{$opt} = print_lxc_network($done);
- write_config($vmid, $conf);
+ PVE::LXC::Config->write_config($vmid, $conf);
}
sub update_ipconfig {
}
}
$conf->{$opt} = print_lxc_network($optdata);
- write_config($vmid, $conf);
+ PVE::LXC::Config->write_config($vmid, $conf);
$lxc_setup->setup_network($conf);
};
}
-# Internal snapshots
-
-# NOTE: Snapshot create/delete involves several non-atomic
-# action, and can take a long time.
-# So we try to avoid locking the file and use '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';
-
- $dest->{$k} = $source->{$k};
- }
+my $enter_namespace = sub {
+ my ($vmid, $pid, $which, $type) = @_;
+ sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
+ or die "failed to open $which namespace of container $vmid: $!\n";
+ PVE::Tools::setns(fileno($fd), $type)
+ or die "failed to enter $which namespace of container $vmid: $!\n";
+ close $fd;
};
-my $snapshot_prepare = sub {
- my ($vmid, $snapname, $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);
+my $do_syncfs = sub {
+ my ($vmid, $pid, $socket) = @_;
- $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);
-
- $snap = $conf->{snapshots}->{$snapname} = {};
-
- &$snapshot_copy_config($conf, $snap);
-
- $snap->{'snapstate'} = "prepare";
- $snap->{'snaptime'} = time();
- $snap->{'description'} = $comment if $comment;
- $conf->{snapshots}->{$snapname} = $snap;
-
- write_config($vmid, $conf);
- };
-
- lock_container($vmid, 10, $updatefn);
-
- return $snap;
-};
+ &$enter_namespace($vmid, $pid, 'mnt', PVE::Tools::CLONE_NEWNS);
-my $snapshot_commit = sub {
- my ($vmid, $snapname) = @_;
+ # Tell the parent process to start reading our /proc/mounts
+ print {$socket} "go\n";
+ $socket->flush();
- my $updatefn = sub {
+ # Receive /proc/self/mounts
+ my $mountdata = do { local $/ = undef; <$socket> };
+ close $socket;
- my $conf = load_config($vmid);
-
- die "missing snapshot lock\n"
- if !($conf->{lock} && $conf->{lock} eq 'snapshot');
-
- die "snapshot '$snapname' does not exist\n"
- if !defined($conf->{snapshots}->{$snapname});
-
- die "wrong snapshot state\n"
- if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
- $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
-
- delete $conf->{snapshots}->{$snapname}->{'snapstate'};
- delete $conf->{lock};
- $conf->{parent} = $snapname;
-
- write_config($vmid, $conf);
- };
-
- lock_container($vmid, 10 ,$updatefn);
+ # Now sync all mountpoints...
+ my $mounts = PVE::ProcFSTools::parse_mounts($mountdata);
+ foreach my $mp (@$mounts) {
+ my ($what, $dir, $fs) = @$mp;
+ next if $fs eq 'fuse.lxcfs';
+ eval { PVE::Tools::sync_mountpoint($dir); };
+ warn $@ if $@;
+ }
};
-sub has_feature {
- my ($feature, $conf, $storecfg, $snapname) = @_;
-
- my $err;
-
- foreach_mountpoint($conf, sub {
- my ($ms, $mountpoint) = @_;
-
- return if $err; # skip further test
-
- $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
-
- # TODO: implement support for mountpoints
- die "unable to handle mountpoint '$ms' - feature not implemented\n"
- if $ms ne 'rootfs';
- });
-
- return $err ? 0 : 1;
-}
-
-sub snapshot_create {
- my ($vmid, $snapname, $comment) = @_;
-
- my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
-
- my $conf = load_config($vmid);
+sub sync_container_namespace {
+ my ($vmid) = @_;
+ my $pid = find_lxc_pid($vmid);
- my $running = check_running($vmid);
- eval {
- if ($running) {
- PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
- PVE::Tools::run_command(['/bin/sync']);
- };
+ # SOCK_DGRAM is nicer for barriers but cannot be slurped
+ socketpair my $pfd, my $cfd, AF_UNIX, SOCK_STREAM, PF_UNSPEC
+ or die "failed to create socketpair: $!\n";
- my $storecfg = PVE::Storage::config();
- my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
- my $volid = $rootinfo->{volume};
+ my $child = fork();
+ die "fork failed: $!\n" if !defined($child);
- if ($running) {
- PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
+ if (!$child) {
+ eval {
+ close $pfd;
+ &$do_syncfs($vmid, $pid, $cfd);
};
-
- PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
- &$snapshot_commit($vmid, $snapname);
- };
- if(my $err = $@) {
- snapshot_delete($vmid, $snapname, 1);
- die "$err\n";
- }
-}
-
-sub snapshot_delete {
- my ($vmid, $snapname, $force) = @_;
-
- my $snap;
-
- my $conf;
-
- my $updatefn = sub {
-
- $conf = load_config($vmid);
-
- die "you can't delete a snapshot if vm is a template\n"
- if is_template($conf);
-
- $snap = $conf->{snapshots}->{$snapname};
-
- check_lock($conf);
-
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
- $snap->{snapstate} = 'delete';
-
- write_config($vmid, $conf);
- };
-
- lock_container($vmid, 10, $updatefn);
-
- my $storecfg = PVE::Storage::config();
-
- my $del_snap = sub {
-
- check_lock($conf);
-
- if ($conf->{parent} eq $snapname) {
- if ($conf->{snapshots}->{$snapname}->{snapname}) {
- $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
- } else {
- delete $conf->{parent};
- }
- }
-
- delete $conf->{snapshots}->{$snapname};
-
- write_config($vmid, $conf);
- };
-
- my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
- my $rootinfo = parse_ct_mountpoint($rootfs);
- my $volid = $rootinfo->{volume};
-
- eval {
- PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
- };
- my $err = $@;
-
- if(!$err || ($err && $force)) {
- lock_container($vmid, 10, $del_snap);
- if ($err) {
- die "Can't delete snapshot: $vmid $snapname $err\n";
+ if (my $err = $@) {
+ warn $err;
+ POSIX::_exit(1);
}
+ POSIX::_exit(0);
}
-}
-
-sub snapshot_rollback {
- my ($vmid, $snapname) = @_;
-
- my $storecfg = PVE::Storage::config();
-
- my $conf = load_config($vmid);
-
- die "you can't rollback if vm is a template\n" if is_template($conf);
-
- my $snap = $conf->{snapshots}->{$snapname};
-
- die "snapshot '$snapname' does not exist\n" if !defined($snap);
-
- my $rootfs = $snap->{rootfs};
- my $rootinfo = parse_ct_mountpoint($rootfs);
- my $volid = $rootinfo->{volume};
-
- PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
-
- my $updatefn = sub {
-
- die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
- if $snap->{snapstate};
-
- check_lock($conf);
-
- system("lxc-stop -n $vmid --kill") if check_running($vmid);
-
- die "unable to rollback vm $vmid: vm is running\n"
- if check_running($vmid);
-
- $conf->{lock} = 'rollback';
-
- my $forcemachine;
-
- # copy snapshot config to current config
-
- my $tmp_conf = $conf;
- &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
- $conf->{snapshots} = $tmp_conf->{snapshots};
- delete $conf->{snaptime};
- delete $conf->{snapname};
- $conf->{parent} = $snapname;
-
- write_config($vmid, $conf);
- };
-
- my $unlockfn = sub {
- delete $conf->{lock};
- write_config($vmid, $conf);
- };
+ close $cfd;
+ my $go = <$pfd>;
+ die "failed to enter container namespace\n" if $go ne "go\n";
- lock_container($vmid, 10, $updatefn);
+ open my $mounts, '<', "/proc/$child/mounts"
+ or die "failed to open container's /proc/mounts: $!\n";
+ my $mountdata = do { local $/ = undef; <$mounts> };
+ close $mounts;
+ print {$pfd} $mountdata;
+ close $pfd;
- PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
-
- lock_container($vmid, 5, $unlockfn);
+ while (waitpid($child, 0) != $child) {}
+ die "failed to sync container namespace\n" if $? != 0;
}
sub template_create {
my $storecfg = PVE::Storage::config();
- my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
+ my $rootinfo = parse_ct_rootfs($conf->{rootfs});
my $volid = $rootinfo->{volume};
die "Template feature is not available for '$volid'\n"
$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) = @_;
-
- my @names = ('rootfs');
-
- for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
- push @names, "mp$i";
- }
-
- return $reverse ? reverse @names : @names;
-}
-
-# The container might have *different* symlinks than the host. realpath/abs_path
-# use the actual filesystem to resolve links.
-sub sanitize_mountpoint {
- my ($mp) = @_;
- $mp = '/' . $mp; # we always start with a slash
- $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
- $mp =~ s@/\./@@g; # collapse /./
- $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
- $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
- $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
- return $mp;
-}
-
-sub foreach_mountpoint_full {
- my ($conf, $reverse, $func) = @_;
-
- foreach my $key (mountpoint_names($reverse)) {
- my $value = $conf->{$key};
- next if !defined($value);
- my $mountpoint = parse_ct_mountpoint($value, 1);
- next if !defined($mountpoint);
-
- # just to be sure: rootfs is /
- my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
- $mountpoint->{mp} = sanitize_mountpoint($path);
-
- $path = $mountpoint->{volume};
- $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
-
- &$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 {
- my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
+ my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
- return 1 if $authuser ne 'root@pam';
-
- foreach my $opt (@$key_list) {
+ return 1 if $authuser eq 'root@pam';
+ my $check = sub {
+ my ($opt, $delete) = @_;
if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
} 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});
+ 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']);
} elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
} else {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
}
+ };
+
+ foreach my $opt (keys %$newconf) {
+ &$check($opt, 0);
+ }
+ foreach my $opt (@$delete) {
+ &$check($opt, 1);
}
return 1;
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};
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) = @_;
- my $volid = $mountpoint->{volume};
- my $mount = $mountpoint->{mp};
-
- return if !$volid || !$mount;
-
- my $image_path = PVE::Storage::path($storage_cfg, $volid);
- my ($vtype, undef, undef, undef, undef, $isBase, $format) =
- PVE::Storage::parse_volname($storage_cfg, $volid);
-
- die "unable to mount base volume - internal error" if $isBase;
-
mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
});
};
if (my $err = $@) {
- warn "mounting container failed - $err";
+ warn "mounting container failed\n";
umount_all($vmid, $storage_cfg, $conf, 1);
+ die $err;
}
return $rootdir;
}
};
+sub query_loopdev {
+ my ($path) = @_;
+ my $found;
+ my $parser = sub {
+ my $line = shift;
+ if ($line =~ m@^(/dev/loop\d+):@) {
+ $found = $1;
+ }
+ };
+ my $cmd = ['losetup', '--associated', $path];
+ PVE::Tools::run_command($cmd, outfunc => $parser);
+ return $found;
+}
+
+# Run a function with a file attached to a loop device.
+# The loop device is always detached afterwards (or set to autoclear).
+# Returns the loop device.
+sub run_with_loopdev {
+ my ($func, $file) = @_;
+ my $device = query_loopdev($file);
+ # Try to reuse an existing device
+ if ($device) {
+ # We assume that whoever setup the loop device is responsible for
+ # detaching it.
+ &$func($device);
+ return $device;
+ }
+
+ my $parser = sub {
+ my $line = shift;
+ if ($line =~ m@^(/dev/loop\d+)$@) {
+ $device = $1;
+ }
+ };
+ PVE::Tools::run_command(['losetup', '--show', '-f', $file], outfunc => $parser);
+ die "failed to setup loop device for $file\n" if !$device;
+ eval { &$func($device); };
+ my $err = $@;
+ PVE::Tools::run_command(['losetup', '-d', $device]);
+ die $err if $err;
+ return $device;
+}
+
+sub bindmount {
+ my ($dir, $dest, $ro, @extra_opts) = @_;
+ PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
+ if ($ro) {
+ eval { PVE::Tools::run_command(['mount', '-o', 'bind,remount,ro', $dest]); };
+ if (my $err = $@) {
+ warn "bindmount error\n";
+ # don't leave writable bind-mounts behind...
+ PVE::Tools::run_command(['umount', $dest]);
+ die $err;
+ }
+ }
+}
+
# use $rootdir = undef to just return the corresponding mount path
sub mountpoint_mount {
my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
my $volid = $mountpoint->{volume};
my $mount = $mountpoint->{mp};
+ my $type = $mountpoint->{type};
+ my $quota = !$snapname && !$mountpoint->{ro} && $mountpoint->{quota};
+ my $mounted_dev;
return if !$volid || !$mount;
die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
+ my $optstring = '';
+ if (defined($mountpoint->{acl})) {
+ $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl');
+ }
+ my $readonly = $mountpoint->{ro};
+
+ my @extra_opts = ('-o', $optstring);
+
if ($storage) {
my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
if ($scfg->{type} eq 'zfspool') {
my $path_arg = $path;
$path_arg =~ s!^/+!!;
- PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
+ PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
} else {
die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
}
} else {
- PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
+ bindmount($path, $mount_path, $readonly, @extra_opts);
+ warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
}
}
- return wantarray ? ($path, 0) : $path;
+ return wantarray ? ($path, 0, $mounted_dev) : $path;
} elsif ($format eq 'raw' || $format eq 'iso') {
+ my $domount = sub {
+ my ($path) = @_;
+ if ($mount_path) {
+ if ($format eq 'iso') {
+ PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
+ } elsif ($isBase || defined($snapname)) {
+ PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
+ } else {
+ if ($quota) {
+ push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
+ }
+ push @extra_opts, '-o', 'ro' if $readonly;
+ PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+ }
+ }
+ };
my $use_loopdev = 0;
- my @extra_opts;
if ($scfg->{path}) {
- push @extra_opts, '-o', 'loop';
+ $mounted_dev = run_with_loopdev($domount, $path);
$use_loopdev = 1;
- } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
- # do nothing
+ } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' ||
+ $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') {
+ $mounted_dev = $path;
+ &$domount($path);
} else {
die "unsupported storage type '$scfg->{type}'\n";
}
- if ($mount_path) {
- if ($format eq 'iso') {
- PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
- } elsif ($isBase || defined($snapname)) {
- PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
- } else {
- PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
- }
- }
- return wantarray ? ($path, $use_loopdev) : $path;
+ return wantarray ? ($path, $use_loopdev, $mounted_dev) : $path;
} else {
die "unsupported image format '$format'\n";
}
- } elsif ($volid =~ m|^/dev/.+|) {
- PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
- return wantarray ? ($volid, 0) : $volid;
- } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
+ } elsif ($type eq 'device') {
+ push @extra_opts, '-o', 'ro' if $readonly;
+ PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
+ return wantarray ? ($volid, 0, $volid) : $volid;
+ } elsif ($type eq 'bind') {
+ die "directory '$volid' does not exist\n" if ! -d $volid;
&$check_mount_path($volid);
- PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
- return wantarray ? ($volid, 0) : $volid;
+ bindmount($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
+ warn "cannot enable quota control for bind mounts\n" if $quota;
+ return wantarray ? ($volid, 0, undef) : $volid;
}
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 || $volid =~ m|^/|;
-
- my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
- return if !$sid;
-
- push @$vollist, $volid;
- });
-
- return $vollist;
-}
-
sub mkfs {
- my ($dev) = @_;
+ my ($dev, $rootuid, $rootgid) = @_;
- PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
+ PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp',
+ '-E', "root_owner=$rootuid:$rootgid",
+ $dev]);
}
sub format_disk {
- my ($storage_cfg, $volid) = @_;
+ my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
if ($volid =~ m!^/dev/.+!) {
mkfs($volid);
die "cannot format volume '$volid' (format == $format)\n"
if $format ne 'raw';
- mkfs($path);
+ mkfs($path, $rootuid, $rootgid);
}
sub destroy_disks {
my $vollist = [];
eval {
- foreach_mountpoint($settings, sub {
+ my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
+ my $chown_vollist = [];
+
+ PVE::LXC::Config->foreach_mountpoint($settings, sub {
my ($ms, $mountpoint) = @_;
my $volid = $mountpoint->{volume};
my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
- return if !$storage;
-
- if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
+ if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
my ($storeid, $size_gb) = ($1, $2);
my $size_kb = int(${size_gb}*1024) * 1024;
if ($size_kb > 0) {
$volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
undef, $size_kb);
- format_disk($storecfg, $volid);
+ format_disk($storecfg, $volid, $rootuid, $rootgid);
} else {
$volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
undef, 0);
+ push @$chown_vollist, $volid;
}
} elsif ($scfg->{type} eq 'zfspool') {
$volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
undef, $size_kb);
- } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
+ push @$chown_vollist, $volid;
+ } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') {
$volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
- format_disk($storecfg, $volid);
+ format_disk($storecfg, $volid, $rootuid, $rootgid);
} elsif ($scfg->{type} eq 'rbd') {
die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
$volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
- format_disk($storecfg, $volid);
+ format_disk($storecfg, $volid, $rootuid, $rootgid);
} else {
die "unable to create containers on storage type '$scfg->{type}'\n";
}
push @$vollist, $volid;
- my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
- $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
+ $mountpoint->{volume} = $volid;
+ $mountpoint->{size} = $size_kb * 1024;
+ $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
} else {
- # use specified/existing volid
+ # use specified/existing volid/dir/device
+ $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
}
});
+
+ PVE::Storage::activate_volumes($storecfg, $chown_vollist, undef);
+ foreach my $volid (@$chown_vollist) {
+ my $path = PVE::Storage::path($storecfg, $volid, undef);
+ chown($rootuid, $rootgid, $path);
+ }
+ PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef);
};
# free allocated images on error
if (my $err = $@) {
return [];
}
+
1;