]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC/CGroup.pm
add PVE::LXC::{CGroup, Command} submodules
[pve-container.git] / src / PVE / LXC / CGroup.pm
1 # cgroup handler
2 #
3 # This package should deal with figuring out the right cgroup path for a
4 # container (via the command socket), reading and writing cgroup values, and
5 # handling cgroup v1 & v2 differences.
6 #
7 # Note that the long term plan is to have resource manage functions intead of
8 # dealing with cgroup files on the outside.
9
10 package PVE::LXC::CGroup;
11
12 use strict;
13 use warnings;
14
15 use PVE::LXC::Command;
16
17 # We don't want to do a command socket round trip for every cgroup read/write,
18 # so any cgroup function needs to have the container's path cached, so this
19 # package has to be instantiated.
20 #
21 # LXC keeps separate paths by controller (although they're normally all the
22 # same, in our # case anyway), so we cache them by controller as well.
23 sub new {
24 my ($class, $vmid) = @_;
25
26 my $self = { vmid => $vmid };
27
28 return bless $self, $class;
29 }
30
31 my $CPUSET_BASE = undef;
32 # Find the cpuset cgroup controller.
33 #
34 # This is a function, not a method!
35 sub cpuset_controller_path() {
36 if (!defined($CPUSET_BASE)) {
37 my $CPUSET_PATHS = [
38 # legacy cpuset cgroup:
39 ['/sys/fs/cgroup/cpuset', 'cpuset.effective_cpus'],
40 # pure cgroupv2 environment:
41 ['/sys/fs/cgroup', 'cpuset.cpus.effective'],
42 # hybrid, with cpuset moved to cgroupv2
43 ['/sys/fs/cgroup/unified', 'cpuset.cpus.effective'],
44 ];
45
46 my ($result) = grep { -f "$_->[0]/$_->[1]" } @$CPUSET_PATHS;
47 die "failed to find cpuset controller\n" if !defined($result);
48
49 $CPUSET_BASE = $result->[0];
50 }
51
52 return $CPUSET_BASE;
53 }
54
55 my $CGROUP_MODE = undef;
56 # Figure out which cgroup mode we're operating under:
57 #
58 # Returns 1 if cgroupv1 controllers exist (hybrid or legacy mode), and 2 in a
59 # cgroupv2-only environment.
60 #
61 # This is a function, not a method!
62 sub cgroup_mode() {
63 if (!defined($CGROUP_MODE)) {
64 my ($v1, $v2) = PVE::LXC::get_cgroup_subsystems();
65 if (keys %$v1) {
66 # hybrid or legacy mode
67 $CGROUP_MODE = 1;
68 } elsif ($v2) {
69 $CGROUP_MODE = 2;
70 }
71 }
72
73 die "unknown cgroup mode\n" if !defined($CGROUP_MODE);
74 return $CGROUP_MODE;
75 }
76
77 # Get a subdirectory (without the cgroup mount point) for a controller.
78 #
79 # If `$controller` is `undef`, get the unified (cgroupv2) path.
80 #
81 # Note that in cgroup v2, lxc uses the activated controller names
82 # (`cgroup.controllers` file) as list of controllers for the unified hierarchy,
83 # so this returns a result when a `controller` is provided even when using
84 # a pure cgroupv2 setup.
85 my sub get_subdir {
86 my ($self, $controller, $limiting) = @_;
87
88 my $entry_name = $controller || 'unified';
89 my $entry = ($self->{controllers}->{$entry_name} //= {});
90
91 my $kind = $limiting ? 'limit' : 'ns';
92 my $path = $entry->{$kind};
93
94 return $path if defined $path;
95
96 $path = PVE::LXC::Command::get_cgroup_path(
97 $self->{vmid},
98 $controller,
99 $limiting,
100 ) or return undef;
101
102 # untaint:
103 if ($path =~ /\.\./) {
104 die "lxc returned suspicious path: '$path'\n";
105 }
106 ($path) = ($path =~ /^(.*)$/s);
107
108 $entry->{$kind} = $path;
109
110 return $path;
111 }
112
113 # Get a path for a controller.
114 #
115 # `$controller` may be `undef`, see get_subdir above for details.
116 sub get_path {
117 my ($self, $controller) = @_;
118
119 my $path = get_subdir($self, $controller)
120 or return undef;
121
122 # The main mount point we currenlty assume to be in a standard location.
123 return "/sys/fs/cgroup/$path" if cgroup_mode() == 2;
124 return "/sys/fs/cgroup/unified/$path" if !defined($controller);
125 return "/sys/fs/cgroup/$controller/$path";
126 }
127
128 1;