]> git.proxmox.com Git - pve-container.git/commitdiff
implement mountpoint hotplugging
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Wed, 20 Nov 2019 07:31:06 +0000 (08:31 +0100)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Wed, 20 Nov 2019 13:59:54 +0000 (14:59 +0100)
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
src/PVE/LXC.pm
src/PVE/LXC/Config.pm

index 26c03f75f692803509266a6ccc39e5067ff98fe1..431f6cd481b372aa2c4e1ebe7ef33fbdf316f83d 100644 (file)
@@ -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) = @_;
index 573eaffa87c1a795a51214a4ba42d469582820da..c34a8922b9725e724522425b7573fdbc2c70fb8a 100644 (file)
@@ -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);
        }
     }