From 50df544c7207da5de0251d61d633ed0147258031 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 10 Feb 2016 09:58:17 +0100 Subject: [PATCH] added quota flag to mountpoints 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. --- 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 f7f468a..5b419bb 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -67,6 +67,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', { @@ -1077,6 +1083,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? @@ -2146,6 +2157,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) = @_; @@ -2153,6 +2185,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; @@ -2206,36 +2240,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"; @@ -2246,7 +2288,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.39.2