]>
Commit | Line | Data |
---|---|---|
d92d0afd | 1 | # Module for lxc related functionality used mostly by our hooks. |
d92d0afd WB |
2 | package PVE::LXC::Tools; |
3 | ||
abcab32d TL |
4 | use strict; |
5 | use warnings; | |
6 | ||
f4c0ba30 WB |
7 | use Errno qw(ENOSYS); |
8 | ||
d92d0afd WB |
9 | use PVE::SafeSyslog; |
10 | ||
11 | # LXC introduced an `lxc.hook.version` property which allows hooks to be executed in different | |
12 | # manners. The old way passes a lot of stuff as command line parameter, the new way passes | |
13 | # environment variables. | |
14 | # | |
15 | # This is supposed to be a common entry point for hooks, consuming the parameters passed by lxc and | |
16 | # passing them on to a subroutine in a defined way. | |
17 | sub lxc_hook($$&) { | |
18 | my ($expected_type, $expected_section, $code) = @_; | |
19 | ||
20 | my ($ct_name, $section, $type); | |
21 | my $namespaces = {}; | |
22 | my $args; | |
23 | ||
24 | my $version = $ENV{LXC_HOOK_VERSION} // '0'; | |
25 | if ($version eq '0') { | |
26 | # Old style hook: | |
27 | $ct_name = shift @ARGV; | |
28 | $section = shift @ARGV; | |
29 | $type = shift @ARGV; | |
30 | ||
31 | if (!defined($ct_name) || !defined($section) || !defined($type)) { | |
32 | die "missing hook parameters, expected to be called by lxc as hook\n"; | |
33 | } | |
34 | ||
35 | if ($ct_name !~ /^\d+$/ || $section ne $expected_section || $type ne $expected_type) { | |
36 | return; | |
37 | } | |
38 | ||
39 | if ($type eq 'stop') { | |
40 | foreach my $ns (@ARGV) { | |
41 | if ($ns =~ /^([^:]+):(.+)$/) { | |
42 | $namespaces->{$1} = $2; | |
43 | } else { | |
44 | die "unrecognized 'stop' hook parameter: $ns\n"; | |
45 | } | |
46 | } | |
47 | } elsif ($type eq 'clone') { | |
48 | $args = [@ARGV]; | |
49 | } | |
50 | } elsif ($version eq '1') { | |
51 | $ct_name = $ENV{LXC_NAME} | |
52 | or die "missing LXC_NAME environment variable\n"; | |
53 | $section = $ENV{LXC_HOOK_SECTION} | |
54 | or die "missing LXC_HOOK_SECTION environment variable\n"; | |
55 | $type = $ENV{LXC_HOOK_TYPE} | |
56 | or die "missing LXC_HOOK_TYPE environment variable\n"; | |
57 | ||
58 | if ($ct_name !~ /^\d+$/ || $section ne $expected_section || $type ne $expected_type) { | |
59 | return; | |
60 | } | |
61 | ||
62 | foreach my $var (keys %$ENV) { | |
63 | if ($var =~ /^LXC_([A-Z]+)_NS$/) { | |
64 | $namespaces->{lc($1)} = $ENV{$1}; | |
65 | } | |
66 | } | |
67 | } else { | |
68 | die "lxc.hook.version $version not supported!\n"; | |
69 | } | |
70 | ||
71 | my $logid = $ENV{PVE_LOG_ID} || "pve-lxc-hook-$section-$type"; | |
72 | initlog($logid); | |
73 | ||
74 | my $common_vars = { | |
75 | ROOTFS_MOUNT => ($ENV{LXC_ROOTFS_MOUNT} or die "missing LXC_ROOTFS_MOUNT env var\n"), | |
76 | ROOTFS_PATH => ($ENV{LXC_ROOTFS_PATH} or die "missing LXC_ROOTFS_PATH env var\n"), | |
77 | CONFIG_FILE => ($ENV{LXC_CONFIG_FILE} or die "missing LXC_CONFIG_FILE env var\n"), | |
78 | }; | |
79 | if (defined(my $target = $ENV{LXC_TARGET})) { | |
80 | $common_vars->{TARGET} = $target; | |
81 | } | |
82 | ||
83 | $code->($ct_name, $common_vars, $namespaces, $args); | |
84 | } | |
85 | ||
39f13cd7 WB |
86 | sub for_current_devices($&) { |
87 | my ($vmid, $code) = @_; | |
88 | ||
89 | my $devlist_file = "/var/lib/lxc/$vmid/devices"; | |
90 | my $fd; | |
91 | ||
92 | if (! open $fd, '<', $devlist_file) { | |
93 | exit 0 if $!{ENOENT}; # If the list is empty the file might not exist. | |
94 | die "failed to open device list: $!\n"; | |
95 | } | |
96 | ||
97 | while (defined(my $line = <$fd>)) { | |
98 | if ($line !~ m@^(b):(\d+):(\d+):/dev/(\S+)\s*$@) { | |
99 | warn "invalid .pve-devices entry: $line\n"; | |
100 | next; | |
101 | } | |
102 | ||
103 | my ($type, $major, $minor, $dev) = ($1, $2, $3, $4); | |
104 | ||
105 | # Don't break out of $root/dev/ | |
106 | if ($dev =~ /\.\./) { | |
107 | warn "skipping illegal device node entry: $dev\n"; | |
108 | next; | |
109 | } | |
110 | ||
111 | # Never expose /dev/loop-control | |
112 | if ($major == 10 && $minor == 237) { | |
113 | warn "skipping illegal device entry (loop-control) for: $dev\n"; | |
114 | next; | |
115 | } | |
116 | ||
117 | $code->($type, $major, $minor, $dev); | |
118 | } | |
119 | close $fd; | |
120 | } | |
121 | ||
122 | sub cgroup_do_write($$) { | |
123 | my ($path, $value) = @_; | |
124 | my $fd; | |
125 | if (!open($fd, '>', $path)) { | |
126 | warn "failed to open cgroup file $path: $!\n"; | |
127 | return 0; | |
128 | } | |
129 | if (!defined syswrite($fd, $value)) { | |
130 | warn "failed to write value $value to cgroup file $path: $!\n"; | |
131 | return 0; | |
132 | } | |
133 | close($fd); | |
134 | return 1; | |
135 | } | |
136 | ||
f4c0ba30 WB |
137 | # Check whether the kernel supports the new mount api. This is used in the pre-start hook and in |
138 | # the hotplugging code. | |
139 | my $cached_can_use_new_mount_api = undef; | |
140 | sub can_use_new_mount_api() { | |
141 | if (!defined($cached_can_use_new_mount_api)) { | |
142 | if (PVE::Tools::move_mount(-1, 0, -1, 0, 0)) { | |
143 | # This should not be possible... | |
144 | die "kernel behaved unexpectedly: move_mount(-1, NULL, -1, NULL) did not fail!\n"; | |
145 | } | |
146 | # On older kernels the syscall doesn't exist and we get ENOSYS. (For newer kernels this call | |
147 | # will fail with EFAULT instead, since we pass in a NULL pointer as file system name.) | |
148 | $cached_can_use_new_mount_api = ($! != ENOSYS); | |
149 | } | |
150 | return $cached_can_use_new_mount_api; | |
151 | } | |
152 | ||
d92d0afd | 153 | 1; |