[pve-devel] [PATCH v2 container] added quota flag to mountpoints

Wolfgang Bumiller w.bumiller at proxmox.com
Wed Feb 10 09:58:17 CET 2016


quotactl(2) requires a path to the device node to work which
means we need to expose them to the container, luckily it
doesn't need r/w access to the device. Also, loop devices
will not detach from the images anymore with them being
still mounted in the monitor's mount namespace (which is
unshared from the host to prevent accidental unmounts via
lxc.monitor.unshare).

Note that quota manipulation currently does not work with
unprivileged containers.
---
Changes since v1:
 *) Rather than putting a .pve-devices into the container's root
    directory we now write it to '/var/lib/lxc/$vmid/devices'
 *) Renamed with_loopdev() to run_with_loopdev() and added comments

 src/Makefile              |  3 +-
 src/PVE/LXC.pm            | 73 +++++++++++++++++++++++++++++++++++++----------
 src/lxc-pve-autodev-hook  | 52 +++++++++++++++++++++++++++++++++
 src/lxc-pve-prestart-hook | 20 ++++++++++++-
 src/lxc-pve.conf          |  1 +
 5 files changed, 132 insertions(+), 17 deletions(-)
 create mode 100755 src/lxc-pve-autodev-hook

diff --git a/src/Makefile b/src/Makefile
index c9ca2ac..4e3b0b6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -40,7 +40,7 @@ pct.conf.5.pod: gen-pct-conf-pod.pl PVE/LXC.pm
 	mv $@.tmp $@
 
 .PHONY: install
-install: pct lxc-pve.conf lxc-pve-prestart-hook lxc-pve-poststop-hook lxcnetaddbr pct.1.pod pct.1.gz pct.conf.5.pod pct.conf.5.gz pve-update-lxc-config pct.bash-completion
+install: pct lxc-pve.conf lxc-pve-prestart-hook lxc-pve-autodev-hook lxc-pve-poststop-hook lxcnetaddbr pct.1.pod pct.1.gz pct.conf.5.pod pct.conf.5.gz pve-update-lxc-config pct.bash-completion
 	perl -I. -T -e "use PVE::CLI::pct; PVE::CLI::pct->verify_api();"
 	install -d ${SBINDIR}
 	install -m 0755 pct ${SBINDIR}
@@ -49,6 +49,7 @@ install: pct lxc-pve.conf lxc-pve-prestart-hook lxc-pve-poststop-hook lxcnetaddb
 	install -m 0755 lxcnetaddbr ${LXC_SCRIPT_DIR}
 	install -d ${LXC_HOOK_DIR}
 	install -m 0755 lxc-pve-prestart-hook ${LXC_HOOK_DIR}
+	install -m 0755 lxc-pve-autodev-hook ${LXC_HOOK_DIR}
 	install -m 0755 lxc-pve-poststop-hook ${LXC_HOOK_DIR}
 	install -d ${LXC_COMMON_CONFIG_DIR}
 	install -m 0644 lxc-pve.conf ${LXC_COMMON_CONFIG_DIR}/01-pve.conf
diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 1f4fec3..97594c1 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -68,6 +68,12 @@ my $rootfs_desc = {
 	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', {
@@ -1117,6 +1123,11 @@ sub update_lxc_config {
 	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?
@@ -2258,6 +2269,27 @@ sub query_loopdev {
     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;
+    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;
+}
+
 # use $rootdir = undef to just return the corresponding mount path
 sub mountpoint_mount {
     my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
@@ -2265,6 +2297,8 @@ sub mountpoint_mount {
     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;
 
@@ -2318,36 +2352,44 @@ sub mountpoint_mount {
 			die "read-only bind mounts not supported\n";
 		    }
 		    PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
+		    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';
+			}
+			PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
+		    }
+		}
+	    };
 	    my $use_loopdev = 0;
 	    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' || $scfg->{type} eq 'lvmthin') {
-		# do nothing
+		$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 ($type eq 'device') {
 	PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
-	return wantarray ? ($volid, 0) : $volid;
+	return wantarray ? ($volid, 0, $volid) : $volid;
     } elsif ($type eq 'bind') {
 	if ($mountpoint->{ro}) {
 	    die "read-only bind mounts not supported\n";
@@ -2358,7 +2400,8 @@ sub mountpoint_mount {
 	die "directory '$volid' does not exist\n" if ! -d $volid;
 	&$check_mount_path($volid);
 	PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
-	return wantarray ? ($volid, 0) : $volid;
+	warn "cannot enable quota control for bind mounts\n" if $quota;
+	return wantarray ? ($volid, 0, undef) : $volid;
     }
     
     die "unsupported storage";
diff --git a/src/lxc-pve-autodev-hook b/src/lxc-pve-autodev-hook
new file mode 100755
index 0000000..fc99641
--- /dev/null
+++ b/src/lxc-pve-autodev-hook
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+package lxc_pve_autodev_hook;
+
+use strict;
+use warnings;
+
+exit 0 if $ENV{LXC_NAME} && $ENV{LXC_NAME} !~ /^\d+$/;
+
+use PVE::Tools;
+
+my $vmid = $ENV{LXC_NAME};
+my $root = $ENV{LXC_ROOTFS_MOUNT};
+
+if (@ARGV != 3 || $ARGV[1] ne 'lxc' || $ARGV[2] ne 'autodev') {
+    die "invalid usage, this is an LXC autodev hook\n";
+}
+
+if ($vmid ne $ARGV[0]) {
+    die "got wrong name: $ARGV[0] while LXC_NAME=$vmid\n";
+}
+
+my $devlist_file = "/var/lib/lxc/$vmid/devices";
+
+open my $fd, '<', $devlist_file;
+if (!$fd) {
+    exit 0 if $!{ENOENT}; # If the list is empty the file might not exist.
+    die "failed to open device list: $!\n";
+}
+
+while (defined(my $line = <$fd>)) {
+    if ($line !~ m@^(b):(\d+):(\d+):/dev/(\S+)\s*$@) {
+	warn "invalid .pve-devices entry: $line\n";
+    }
+    my ($type, $major, $minor, $dev) = ($1, $2, $3, $4);
+
+    # Don't break out of $root/dev/
+    if ($dev =~ /\.\./) {
+	warn "skipping illegal device node entry: $dev\n";
+	next;
+    }
+
+    # Never expose /dev/loop-control
+    if ($major == 10 && $minor == 237) {
+	warn "skipping illegal device entry (loop-control) for: $dev\n";
+	next;
+    }
+
+    PVE::Tools::run_command(['mknod', '-m', '666', "$root/dev/$dev",
+                             $type, $major, $minor]);
+}
+close $fd;
diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook
index 4ec549a..8017e2c 100755
--- a/src/lxc-pve-prestart-hook
+++ b/src/lxc-pve-prestart-hook
@@ -74,17 +74,35 @@ __PACKAGE__->register_method ({
 
 	my $rootdir = $param->{rootfs};
 
+	my $devlist_file = "/var/lib/lxc/$vmid/devices";
+	unlink $devlist_file;
+	my $devices = [];
+
 	my $setup_mountpoint = sub {
 	    my ($ms, $mountpoint) = @_;
 
 	    #return if $ms eq 'rootfs';
-	    PVE::LXC::mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+	    my (undef, undef, $dev) = PVE::LXC::mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
+	    push @$devices, $dev if $dev && $mountpoint->{quota};
 	};
 
 	PVE::LXC::foreach_mountpoint($conf, $setup_mountpoint);
 
 	my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
 	$lxc_setup->pre_start_hook();
+
+	if (@$devices) {
+	    open my $devlist, '>', $devlist_file
+		or die "failed to create device list\n";
+	    foreach my $dev (@$devices) {
+		my ($mode, $rdev) = (stat($dev))[2,6];
+		next if !$mode || !S_ISBLK($mode) || !$rdev;
+		my $major = int($rdev / 0x100);
+		my $minor = $rdev % 0x100;
+		print {$devlist} "b:$major:$minor:$dev\n";
+	    }
+	    close $devlist;
+	}
 	return undef;
     }});
 
diff --git a/src/lxc-pve.conf b/src/lxc-pve.conf
index 07a1818..3635b44 100644
--- a/src/lxc-pve.conf
+++ b/src/lxc-pve.conf
@@ -1,3 +1,4 @@
 lxc.hook.pre-start = /usr/share/lxc/hooks/lxc-pve-prestart-hook
+lxc.hook.autodev = /usr/share/lxc/hooks/lxc-pve-autodev-hook
 lxc.hook.post-stop = /usr/share/lxc/hooks/lxc-pve-poststop-hook
 x
-- 
2.1.4





More information about the pve-devel mailing list