From: Wolfgang Bumiller Date: Wed, 20 Nov 2019 07:31:06 +0000 (+0100) Subject: implement mountpoint hotplugging X-Git-Url: https://git.proxmox.com/?p=pve-container.git;a=commitdiff_plain;h=b2de4c048ee50094593f4f8ffd18b6c346f7157a implement mountpoint hotplugging Signed-off-by: Wolfgang Bumiller --- diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index 26c03f7..431f6cd 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -1648,6 +1648,44 @@ sub __mountpoint_mount { die "unsupported storage"; } +sub mountpoint_hotplug($$$) { + my ($vmid, $conf, $opt, $mp, $storage_cfg) = @_; + + my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); + + # We do the rest in a fork with an unshared mount namespace, since we're now going to 'stage' + # the mountpoint, then grab it, then move into the container's namespace, then mount it. + + PVE::Tools::run_fork(sub { + # Pin the container pid longer, we also need to get its monitor/parent: + my ($ct_pid, $ct_pidfd) = open_lxc_pid($vmid) + or die "failed to open pidfd of container $vmid\'s init process\n"; + + my ($monitor_pid, $monitor_pidfd) = open_ppid($ct_pid) + or die "failed to open pidfd of container $vmid\'s monitor process\n"; + + my $ct_mnt_ns = $get_container_namespace->($vmid, $ct_pid, 'mnt'); + my $monitor_mnt_ns = $get_container_namespace->($vmid, $monitor_pid, 'mnt'); + + # Change into the monitor's mount namespace. We "pin" the mount into the monitor's + # namespace for it to remain active there since the container will be able to unmount + # hotplugged mount points and thereby potentially free up loop devices, which is a security + # concern. + PVE::Tools::setns(fileno($monitor_mnt_ns), PVE::Tools::CLONE_NEWNS); + chdir('/') + or die "failed to change root directory within the monitor's mount namespace: $!\n"; + + my $dir = get_staging_mount_path($opt); + my $mount_fd = mountpoint_stage($mp, $dir, $storage_cfg, undef, $rootuid, $rootgid); + + PVE::Tools::setns(fileno($ct_mnt_ns), PVE::Tools::CLONE_NEWNS); + chdir('/') + or die "failed to change root directory within the container's mount namespace: $!\n"; + + mountpoint_insert_staged($mount_fd, undef, $mp->{mp}, $opt, $rootuid, $rootgid); + }); +} + # Create a directory in the mountpoint staging tempfs. sub get_staging_mount_path($) { my ($opt) = @_; diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index 573eaff..c34a892 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -1218,6 +1218,14 @@ sub vmconfig_hotplug_pending { if (!$hotplug_memory_done) { # don't call twice if both opts are passed $hotplug_memory->($conf->{pending}->{memory}, $conf->{pending}->{swap}); } + } elsif ($opt =~ m/^mp(\d+)$/) { + if (!PVE::LXC::Tools::can_use_new_mount_api()) { + die "skip\n"; + } + + $class->apply_pending_mountpoint($vmid, $conf, $opt, $storecfg, 1); + # apply_pending_mountpoint modifies the value if it creates a new disk + $value = $conf->{pending}->{$opt}; } else { die "skip\n"; # skip non-hotpluggable } @@ -1307,15 +1315,36 @@ sub apply_pending_mountpoint { my $old = $conf->{$opt}; if ($mp->{type} eq 'volume') { if ($mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) { + my $original_value = $conf->{pending}->{$opt}; my $vollist = PVE::LXC::create_disks( $storecfg, $vmid, - { $opt => $conf->{pending}->{$opt} }, + { $opt => $original_value }, $conf, 1, ); + if ($running) { + # Re-parse mount point: + my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt}); + eval { + PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg); + }; + my $err = $@; + if ($err) { + PVE::LXC::destroy_disks($storecfg, $vollist); + # The pending-changes code collects errors but keeps on looping through further + # pending changes, so unroll the change in $conf as well if destroy_disks() + # didn't die(). + $conf->{pending}->{$opt} = $original_value; + die $err; + } + } } else { + die "skip\n" if $running && defined($old); # TODO: "changing" mount points? $rescan_volume->($storecfg, $mp); + if ($running) { + PVE::LXC::mountpoint_hotplug($vmid, $conf, $opt, $mp, $storecfg); + } $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp); } }