]>
Commit | Line | Data |
---|---|---|
f76a2828 DM |
1 | package PVE::LXC; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
d14a9a1b | 5 | use POSIX qw(EINTR); |
f76a2828 DM |
6 | |
7 | use File::Path; | |
2cfae16e WB |
8 | use File::Spec; |
9 | use Cwd qw(); | |
f76a2828 DM |
10 | use Fcntl ':flock'; |
11 | ||
12 | use PVE::Cluster qw(cfs_register_file cfs_read_file); | |
c65e0a6d | 13 | use PVE::Storage; |
f76a2828 DM |
14 | use PVE::SafeSyslog; |
15 | use PVE::INotify; | |
a3249355 | 16 | use PVE::JSONSchema qw(get_standard_option); |
6aca6740 | 17 | use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach); |
68fba17b | 18 | use PVE::Network; |
52389a07 | 19 | use PVE::AccessControl; |
228a5a1d | 20 | use PVE::ProcFSTools; |
688afc63 | 21 | use Time::HiRes qw (gettimeofday); |
f76a2828 DM |
22 | |
23 | use Data::Dumper; | |
24 | ||
27916659 DM |
25 | my $nodename = PVE::INotify::nodename(); |
26 | ||
688afc63 WL |
27 | my $cpuinfo= PVE::ProcFSTools::read_cpuinfo(); |
28 | ||
f9897acd | 29 | our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls', |
fc4e132e WB |
30 | '--xattrs', |
31 | '--xattrs-include=user.*', | |
4132377b WB |
32 | '--xattrs-include=security.capability', |
33 | '--warning=no-xattr-write' ]; | |
fc4e132e | 34 | |
27916659 | 35 | cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config); |
f76a2828 | 36 | |
769fbfab WB |
37 | my $rootfs_desc = { |
38 | volume => { | |
39 | type => 'string', | |
40 | default_key => 1, | |
da990295 | 41 | format => 'pve-lxc-mp-string', |
769fbfab WB |
42 | format_description => 'volume', |
43 | description => 'Volume, device or directory to mount into the container.', | |
44 | }, | |
45 | backup => { | |
46 | type => 'boolean', | |
47 | format_description => '[1|0]', | |
48 | description => 'Whether to include the mountpoint in backups.', | |
49 | optional => 1, | |
50 | }, | |
51 | size => { | |
72d583ff WB |
52 | type => 'string', |
53 | format => 'disk-size', | |
769fbfab | 54 | format_description => 'DiskSize', |
769fbfab WB |
55 | description => 'Volume size (read only value).', |
56 | optional => 1, | |
57 | }, | |
471dd315 WB |
58 | acl => { |
59 | type => 'boolean', | |
60 | format_description => 'acl', | |
61 | description => 'Explicitly enable or disable ACL support.', | |
62 | optional => 1, | |
63 | }, | |
64 | ro => { | |
65 | type => 'boolean', | |
66 | format_description => 'ro', | |
67 | description => 'Read-only mountpoint (not supported with bind mounts)', | |
68 | optional => 1, | |
69 | }, | |
769fbfab | 70 | }; |
822de0c3 | 71 | |
27916659 | 72 | PVE::JSONSchema::register_standard_option('pve-ct-rootfs', { |
769fbfab | 73 | type => 'string', format => $rootfs_desc, |
8fbd2935 | 74 | description => "Use volume as container root.", |
27916659 DM |
75 | optional => 1, |
76 | }); | |
77 | ||
52389a07 DM |
78 | PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', { |
79 | description => "The name of the snapshot.", | |
80 | type => 'string', format => 'pve-configid', | |
81 | maxLength => 40, | |
82 | }); | |
83 | ||
27916659 | 84 | my $confdesc = { |
09d3ec42 DM |
85 | lock => { |
86 | optional => 1, | |
87 | type => 'string', | |
88 | description => "Lock/unlock the VM.", | |
89 | enum => [qw(migrate backup snapshot rollback)], | |
90 | }, | |
27916659 DM |
91 | onboot => { |
92 | optional => 1, | |
93 | type => 'boolean', | |
94 | description => "Specifies whether a VM will be started during system bootup.", | |
95 | default => 0, | |
117636e5 | 96 | }, |
27916659 | 97 | startup => get_standard_option('pve-startup-order'), |
bb1ac2de DM |
98 | template => { |
99 | optional => 1, | |
100 | type => 'boolean', | |
101 | description => "Enable/disable Template.", | |
102 | default => 0, | |
103 | }, | |
27916659 DM |
104 | arch => { |
105 | optional => 1, | |
106 | type => 'string', | |
107 | enum => ['amd64', 'i386'], | |
108 | description => "OS architecture type.", | |
109 | default => 'amd64', | |
117636e5 | 110 | }, |
27916659 DM |
111 | ostype => { |
112 | optional => 1, | |
113 | type => 'string', | |
fa7cb12b | 114 | enum => ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux'], |
27916659 | 115 | description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.", |
a3249355 | 116 | }, |
4f958489 DM |
117 | console => { |
118 | optional => 1, | |
119 | type => 'boolean', | |
120 | description => "Attach a console device (/dev/console) to the container.", | |
121 | default => 1, | |
122 | }, | |
27916659 DM |
123 | tty => { |
124 | optional => 1, | |
125 | type => 'integer', | |
126 | description => "Specify the number of tty available to the container", | |
127 | minimum => 0, | |
128 | maximum => 6, | |
0d0ca400 | 129 | default => 2, |
611fe3aa | 130 | }, |
27916659 DM |
131 | cpulimit => { |
132 | optional => 1, | |
133 | type => 'number', | |
c31ad455 | 134 | description => "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.", |
27916659 DM |
135 | minimum => 0, |
136 | maximum => 128, | |
137 | default => 0, | |
138 | }, | |
139 | cpuunits => { | |
140 | optional => 1, | |
141 | type => 'integer', | |
c31ad455 | 142 | description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.", |
27916659 DM |
143 | minimum => 0, |
144 | maximum => 500000, | |
81bee809 | 145 | default => 1024, |
27916659 DM |
146 | }, |
147 | memory => { | |
148 | optional => 1, | |
149 | type => 'integer', | |
150 | description => "Amount of RAM for the VM in MB.", | |
151 | minimum => 16, | |
152 | default => 512, | |
153 | }, | |
154 | swap => { | |
155 | optional => 1, | |
156 | type => 'integer', | |
157 | description => "Amount of SWAP for the VM in MB.", | |
158 | minimum => 0, | |
159 | default => 512, | |
160 | }, | |
161 | hostname => { | |
162 | optional => 1, | |
163 | description => "Set a host name for the container.", | |
159aad3e | 164 | type => 'string', format => 'dns-name', |
27916659 DM |
165 | maxLength => 255, |
166 | }, | |
167 | description => { | |
168 | optional => 1, | |
169 | type => 'string', | |
170 | description => "Container description. Only used on the configuration web interface.", | |
171 | }, | |
172 | searchdomain => { | |
173 | optional => 1, | |
159aad3e | 174 | type => 'string', format => 'dns-name-list', |
c31ad455 | 175 | description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.", |
27916659 DM |
176 | }, |
177 | nameserver => { | |
178 | optional => 1, | |
159aad3e | 179 | type => 'string', format => 'address-list', |
c31ad455 | 180 | description => "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.", |
27916659 DM |
181 | }, |
182 | rootfs => get_standard_option('pve-ct-rootfs'), | |
09d3ec42 DM |
183 | parent => { |
184 | optional => 1, | |
185 | type => 'string', format => 'pve-configid', | |
186 | maxLength => 40, | |
187 | description => "Parent snapshot name. This is used internally, and should not be modified.", | |
188 | }, | |
189 | snaptime => { | |
190 | optional => 1, | |
191 | description => "Timestamp for snapshots.", | |
192 | type => 'integer', | |
193 | minimum => 0, | |
194 | }, | |
aca816ad DM |
195 | cmode => { |
196 | optional => 1, | |
197 | description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).", | |
198 | type => 'string', | |
199 | enum => ['shell', 'console', 'tty'], | |
200 | default => 'tty', | |
201 | }, | |
7e806596 AG |
202 | protection => { |
203 | optional => 1, | |
204 | type => 'boolean', | |
c31ad455 | 205 | description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.", |
7e806596 AG |
206 | default => 0, |
207 | }, | |
425b62cb WB |
208 | unprivileged => { |
209 | optional => 1, | |
210 | type => 'boolean', | |
211 | description => "Makes the container run as unprivileged user. (Should not be modified manually.)", | |
212 | default => 0, | |
213 | }, | |
f76a2828 DM |
214 | }; |
215 | ||
e576f689 DM |
216 | my $valid_lxc_conf_keys = { |
217 | 'lxc.include' => 1, | |
218 | 'lxc.arch' => 1, | |
219 | 'lxc.utsname' => 1, | |
220 | 'lxc.haltsignal' => 1, | |
221 | 'lxc.rebootsignal' => 1, | |
222 | 'lxc.stopsignal' => 1, | |
223 | 'lxc.init_cmd' => 1, | |
224 | 'lxc.network.type' => 1, | |
225 | 'lxc.network.flags' => 1, | |
226 | 'lxc.network.link' => 1, | |
227 | 'lxc.network.mtu' => 1, | |
228 | 'lxc.network.name' => 1, | |
229 | 'lxc.network.hwaddr' => 1, | |
230 | 'lxc.network.ipv4' => 1, | |
231 | 'lxc.network.ipv4.gateway' => 1, | |
232 | 'lxc.network.ipv6' => 1, | |
233 | 'lxc.network.ipv6.gateway' => 1, | |
234 | 'lxc.network.script.up' => 1, | |
235 | 'lxc.network.script.down' => 1, | |
236 | 'lxc.pts' => 1, | |
237 | 'lxc.console.logfile' => 1, | |
238 | 'lxc.console' => 1, | |
239 | 'lxc.tty' => 1, | |
240 | 'lxc.devttydir' => 1, | |
241 | 'lxc.hook.autodev' => 1, | |
242 | 'lxc.autodev' => 1, | |
243 | 'lxc.kmsg' => 1, | |
244 | 'lxc.mount' => 1, | |
245 | 'lxc.mount.entry' => 1, | |
246 | 'lxc.mount.auto' => 1, | |
312e9850 | 247 | 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs', |
e576f689 | 248 | 'lxc.rootfs.mount' => 1, |
312e9850 WB |
249 | 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' . |
250 | ', please use mountpoint options in the "rootfs" key', | |
e576f689 DM |
251 | # lxc.cgroup.* |
252 | 'lxc.cap.drop' => 1, | |
253 | 'lxc.cap.keep' => 1, | |
254 | 'lxc.aa_profile' => 1, | |
255 | 'lxc.aa_allow_incomplete' => 1, | |
256 | 'lxc.se_context' => 1, | |
257 | 'lxc.seccomp' => 1, | |
258 | 'lxc.id_map' => 1, | |
259 | 'lxc.hook.pre-start' => 1, | |
260 | 'lxc.hook.pre-mount' => 1, | |
261 | 'lxc.hook.mount' => 1, | |
262 | 'lxc.hook.start' => 1, | |
53775872 | 263 | 'lxc.hook.stop' => 1, |
e576f689 DM |
264 | 'lxc.hook.post-stop' => 1, |
265 | 'lxc.hook.clone' => 1, | |
266 | 'lxc.hook.destroy' => 1, | |
267 | 'lxc.loglevel' => 1, | |
268 | 'lxc.logfile' => 1, | |
269 | 'lxc.start.auto' => 1, | |
270 | 'lxc.start.delay' => 1, | |
271 | 'lxc.start.order' => 1, | |
272 | 'lxc.group' => 1, | |
273 | 'lxc.environment' => 1, | |
e576f689 DM |
274 | }; |
275 | ||
769fbfab WB |
276 | my $netconf_desc = { |
277 | type => { | |
278 | type => 'string', | |
279 | optional => 1, | |
280 | description => "Network interface type.", | |
281 | enum => [qw(veth)], | |
282 | }, | |
283 | name => { | |
284 | type => 'string', | |
285 | format_description => 'String', | |
286 | description => 'Name of the network device as seen from inside the container. (lxc.network.name)', | |
287 | pattern => '[-_.\w\d]+', | |
288 | }, | |
289 | bridge => { | |
290 | type => 'string', | |
291 | format_description => 'vmbr<Number>', | |
292 | description => 'Bridge to attach the network device to.', | |
293 | pattern => '[-_.\w\d]+', | |
a7c080a7 | 294 | optional => 1, |
769fbfab WB |
295 | }, |
296 | hwaddr => { | |
297 | type => 'string', | |
298 | format_description => 'MAC', | |
299 | description => 'Bridge to attach the network device to. (lxc.network.hwaddr)', | |
300 | pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i, | |
301 | optional => 1, | |
302 | }, | |
303 | mtu => { | |
304 | type => 'integer', | |
305 | format_description => 'Number', | |
306 | description => 'Maximum transfer unit of the interface. (lxc.network.mtu)', | |
07521af1 | 307 | minimum => 64, # minimum ethernet frame is 64 bytes |
769fbfab WB |
308 | optional => 1, |
309 | }, | |
310 | ip => { | |
311 | type => 'string', | |
312 | format => 'pve-ipv4-config', | |
313 | format_description => 'IPv4Format/CIDR', | |
314 | description => 'IPv4 address in CIDR format.', | |
315 | optional => 1, | |
316 | }, | |
317 | gw => { | |
318 | type => 'string', | |
319 | format => 'ipv4', | |
320 | format_description => 'GatewayIPv4', | |
321 | description => 'Default gateway for IPv4 traffic.', | |
322 | optional => 1, | |
323 | }, | |
324 | ip6 => { | |
325 | type => 'string', | |
326 | format => 'pve-ipv6-config', | |
327 | format_description => 'IPv6Format/CIDR', | |
328 | description => 'IPv6 address in CIDR format.', | |
329 | optional => 1, | |
330 | }, | |
331 | gw6 => { | |
332 | type => 'string', | |
333 | format => 'ipv6', | |
334 | format_description => 'GatewayIPv6', | |
335 | description => 'Default gateway for IPv6 traffic.', | |
336 | optional => 1, | |
337 | }, | |
338 | firewall => { | |
339 | type => 'boolean', | |
340 | format_description => '[1|0]', | |
341 | description => "Controls whether this interface's firewall rules should be used.", | |
342 | optional => 1, | |
343 | }, | |
344 | tag => { | |
345 | type => 'integer', | |
346 | format_description => 'VlanNo', | |
347 | minimum => '2', | |
348 | maximum => '4094', | |
23eb2244 WB |
349 | description => "VLAN tag for this interface.", |
350 | optional => 1, | |
351 | }, | |
352 | trunks => { | |
353 | type => 'string', | |
354 | pattern => qr/\d+(?:;\d+)*/, | |
355 | format_description => 'vlanid[;vlanid...]', | |
356 | description => "VLAN ids to pass through the interface", | |
769fbfab WB |
357 | optional => 1, |
358 | }, | |
359 | }; | |
360 | PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc); | |
361 | ||
27916659 DM |
362 | my $MAX_LXC_NETWORKS = 10; |
363 | for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) { | |
364 | $confdesc->{"net$i"} = { | |
365 | optional => 1, | |
1a0a239c | 366 | type => 'string', format => $netconf_desc, |
769fbfab | 367 | description => "Specifies network interfaces for the container.", |
27916659 | 368 | }; |
90bc31f7 DM |
369 | } |
370 | ||
da990295 DC |
371 | PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string); |
372 | sub verify_lxc_mp_string{ | |
373 | my ($mp, $noerr) = @_; | |
374 | ||
375 | # do not allow: | |
376 | # /./ or /../ | |
377 | # /. or /.. at the end | |
378 | # ../ at the beginning | |
379 | ||
380 | if($mp =~ m@/\.\.?/@ || | |
381 | $mp =~ m@/\.\.?$@ || | |
382 | $mp =~ m@^\.\./@){ | |
383 | return undef if $noerr; | |
384 | die "$mp contains illegal character sequences\n"; | |
385 | } | |
386 | return $mp; | |
387 | } | |
388 | ||
769fbfab WB |
389 | my $mp_desc = { |
390 | %$rootfs_desc, | |
391 | mp => { | |
392 | type => 'string', | |
da990295 | 393 | format => 'pve-lxc-mp-string', |
769fbfab WB |
394 | format_description => 'Path', |
395 | description => 'Path to the mountpoint as seen from inside the container.', | |
769fbfab WB |
396 | }, |
397 | }; | |
398 | PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc); | |
399 | ||
69202f71 WB |
400 | my $unuseddesc = { |
401 | optional => 1, | |
402 | type => 'string', format => 'pve-volume-id', | |
403 | description => "Reference to unused volumes.", | |
404 | }; | |
405 | ||
02c9d10c AD |
406 | my $MAX_MOUNT_POINTS = 10; |
407 | for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) { | |
408 | $confdesc->{"mp$i"} = { | |
409 | optional => 1, | |
769fbfab | 410 | type => 'string', format => $mp_desc, |
566d5f81 | 411 | description => "Use volume as container mount point (experimental feature).", |
02c9d10c AD |
412 | optional => 1, |
413 | }; | |
414 | } | |
415 | ||
69202f71 WB |
416 | my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS; |
417 | for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) { | |
418 | $confdesc->{"unused$i"} = $unuseddesc; | |
419 | } | |
420 | ||
27916659 DM |
421 | sub write_pct_config { |
422 | my ($filename, $conf) = @_; | |
f76a2828 | 423 | |
27916659 | 424 | delete $conf->{snapstate}; # just to be sure |
f76a2828 | 425 | |
27916659 DM |
426 | my $generate_raw_config = sub { |
427 | my ($conf) = @_; | |
f76a2828 | 428 | |
27916659 | 429 | my $raw = ''; |
cbb03fea | 430 | |
27916659 DM |
431 | # add description as comment to top of file |
432 | my $descr = $conf->{description} || ''; | |
433 | foreach my $cl (split(/\n/, $descr)) { | |
434 | $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; | |
a12a36e0 | 435 | } |
fff3a342 | 436 | |
27916659 | 437 | foreach my $key (sort keys %$conf) { |
09d3ec42 | 438 | next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || |
e576f689 | 439 | $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc'; |
545d10ba DM |
440 | my $value = $conf->{$key}; |
441 | die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/; | |
442 | $raw .= "$key: $value\n"; | |
a12a36e0 | 443 | } |
e576f689 DM |
444 | |
445 | if (my $lxcconf = $conf->{lxc}) { | |
446 | foreach my $entry (@$lxcconf) { | |
447 | my ($k, $v) = @$entry; | |
448 | $raw .= "$k: $v\n"; | |
449 | } | |
450 | } | |
451 | ||
27916659 | 452 | return $raw; |
a12a36e0 | 453 | }; |
160f0941 | 454 | |
27916659 | 455 | my $raw = &$generate_raw_config($conf); |
a12a36e0 | 456 | |
27916659 DM |
457 | foreach my $snapname (sort keys %{$conf->{snapshots}}) { |
458 | $raw .= "\n[$snapname]\n"; | |
459 | $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname}); | |
f76a2828 DM |
460 | } |
461 | ||
f76a2828 DM |
462 | return $raw; |
463 | } | |
464 | ||
27916659 DM |
465 | sub check_type { |
466 | my ($key, $value) = @_; | |
822de0c3 | 467 | |
27916659 | 468 | die "unknown setting '$key'\n" if !$confdesc->{$key}; |
822de0c3 | 469 | |
27916659 DM |
470 | my $type = $confdesc->{$key}->{type}; |
471 | ||
472 | if (!defined($value)) { | |
473 | die "got undefined value\n"; | |
474 | } | |
475 | ||
476 | if ($value =~ m/[\n\r]/) { | |
477 | die "property contains a line feed\n"; | |
478 | } | |
822de0c3 | 479 | |
27916659 DM |
480 | if ($type eq 'boolean') { |
481 | return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); | |
482 | return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); | |
483 | die "type check ('boolean') failed - got '$value'\n"; | |
484 | } elsif ($type eq 'integer') { | |
485 | return int($1) if $value =~ m/^(\d+)$/; | |
486 | die "type check ('integer') failed - got '$value'\n"; | |
487 | } elsif ($type eq 'number') { | |
488 | return $value if $value =~ m/^(\d+)(\.\d+)?$/; | |
489 | die "type check ('number') failed - got '$value'\n"; | |
490 | } elsif ($type eq 'string') { | |
491 | if (my $fmt = $confdesc->{$key}->{format}) { | |
492 | PVE::JSONSchema::check_format($fmt, $value); | |
493 | return $value; | |
494 | } | |
cbb03fea | 495 | return $value; |
822de0c3 | 496 | } else { |
27916659 | 497 | die "internal error" |
822de0c3 | 498 | } |
822de0c3 DM |
499 | } |
500 | ||
27916659 | 501 | sub parse_pct_config { |
f76a2828 DM |
502 | my ($filename, $raw) = @_; |
503 | ||
504 | return undef if !defined($raw); | |
505 | ||
27916659 | 506 | my $res = { |
f76a2828 | 507 | digest => Digest::SHA::sha1_hex($raw), |
27916659 | 508 | snapshots => {}, |
f76a2828 DM |
509 | }; |
510 | ||
27916659 | 511 | $filename =~ m|/lxc/(\d+).conf$| |
f76a2828 DM |
512 | || die "got strange filename '$filename'"; |
513 | ||
514 | my $vmid = $1; | |
515 | ||
27916659 DM |
516 | my $conf = $res; |
517 | my $descr = ''; | |
518 | my $section = ''; | |
519 | ||
520 | my @lines = split(/\n/, $raw); | |
521 | foreach my $line (@lines) { | |
522 | next if $line =~ m/^\s*$/; | |
523 | ||
524 | if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) { | |
525 | $section = $1; | |
526 | $conf->{description} = $descr if $descr; | |
527 | $descr = ''; | |
528 | $conf = $res->{snapshots}->{$section} = {}; | |
529 | next; | |
a12a36e0 | 530 | } |
a12a36e0 | 531 | |
27916659 DM |
532 | if ($line =~ m/^\#(.*)\s*$/) { |
533 | $descr .= PVE::Tools::decode_text($1) . "\n"; | |
534 | next; | |
f76a2828 | 535 | } |
5d186e16 | 536 | |
545d10ba | 537 | if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) { |
e576f689 DM |
538 | my $key = $1; |
539 | my $value = $3; | |
a23d627d WB |
540 | my $validity = $valid_lxc_conf_keys->{$key} || 0; |
541 | if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) { | |
e576f689 | 542 | push @{$conf->{lxc}}, [$key, $value]; |
a23d627d | 543 | } elsif (my $errmsg = $validity) { |
312e9850 | 544 | warn "vm $vmid - $key: $errmsg\n"; |
e576f689 DM |
545 | } else { |
546 | warn "vm $vmid - unable to parse config: $line\n"; | |
547 | } | |
548 | } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) { | |
27916659 DM |
549 | $descr .= PVE::Tools::decode_text($2); |
550 | } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) { | |
551 | $conf->{snapstate} = $1; | |
fe9a4ab3 | 552 | } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) { |
27916659 | 553 | my $key = $1; |
5d186e16 | 554 | my $value = $2; |
27916659 DM |
555 | eval { $value = check_type($key, $value); }; |
556 | warn "vm $vmid - unable to parse value of '$key' - $@" if $@; | |
557 | $conf->{$key} = $value; | |
5d186e16 | 558 | } else { |
27916659 | 559 | warn "vm $vmid - unable to parse config: $line\n"; |
5d186e16 | 560 | } |
7dfc49cc DM |
561 | } |
562 | ||
27916659 | 563 | $conf->{description} = $descr if $descr; |
5d186e16 | 564 | |
27916659 DM |
565 | delete $res->{snapstate}; # just to be sure |
566 | ||
567 | return $res; | |
f76a2828 DM |
568 | } |
569 | ||
570 | sub config_list { | |
571 | my $vmlist = PVE::Cluster::get_vmlist(); | |
572 | my $res = {}; | |
573 | return $res if !$vmlist || !$vmlist->{ids}; | |
574 | my $ids = $vmlist->{ids}; | |
575 | ||
576 | foreach my $vmid (keys %$ids) { | |
577 | next if !$vmid; # skip CT0 | |
578 | my $d = $ids->{$vmid}; | |
579 | next if !$d->{node} || $d->{node} ne $nodename; | |
580 | next if !$d->{type} || $d->{type} ne 'lxc'; | |
581 | $res->{$vmid}->{type} = 'lxc'; | |
582 | } | |
583 | return $res; | |
584 | } | |
585 | ||
586 | sub cfs_config_path { | |
587 | my ($vmid, $node) = @_; | |
588 | ||
589 | $node = $nodename if !$node; | |
27916659 | 590 | return "nodes/$node/lxc/$vmid.conf"; |
f76a2828 DM |
591 | } |
592 | ||
9c2d4ce9 DM |
593 | sub config_file { |
594 | my ($vmid, $node) = @_; | |
595 | ||
596 | my $cfspath = cfs_config_path($vmid, $node); | |
597 | return "/etc/pve/$cfspath"; | |
598 | } | |
599 | ||
f76a2828 | 600 | sub load_config { |
d18499cf | 601 | my ($vmid, $node) = @_; |
f76a2828 | 602 | |
d18499cf TL |
603 | $node = $nodename if !$node; |
604 | my $cfspath = cfs_config_path($vmid, $node); | |
f76a2828 DM |
605 | |
606 | my $conf = PVE::Cluster::cfs_read_file($cfspath); | |
c31ad455 | 607 | die "container $vmid does not exist\n" if !defined($conf); |
f76a2828 DM |
608 | |
609 | return $conf; | |
610 | } | |
611 | ||
5b4657d0 DM |
612 | sub create_config { |
613 | my ($vmid, $conf) = @_; | |
614 | ||
615 | my $dir = "/etc/pve/nodes/$nodename/lxc"; | |
616 | mkdir $dir; | |
617 | ||
5b4657d0 DM |
618 | write_config($vmid, $conf); |
619 | } | |
620 | ||
621 | sub destroy_config { | |
622 | my ($vmid) = @_; | |
623 | ||
27916659 | 624 | unlink config_file($vmid, $nodename); |
5b4657d0 DM |
625 | } |
626 | ||
f76a2828 DM |
627 | sub write_config { |
628 | my ($vmid, $conf) = @_; | |
629 | ||
630 | my $cfspath = cfs_config_path($vmid); | |
631 | ||
632 | PVE::Cluster::cfs_write_file($cfspath, $conf); | |
633 | } | |
634 | ||
d14a9a1b | 635 | # flock: we use one file handle per process, so lock file |
c31ad455 | 636 | # can be called multiple times and will succeed for the same process. |
d14a9a1b DM |
637 | |
638 | my $lock_handles = {}; | |
639 | my $lockdir = "/run/lock/lxc"; | |
640 | ||
641 | sub lock_filename { | |
642 | my ($vmid) = @_; | |
cbb03fea | 643 | |
53396388 | 644 | return "$lockdir/pve-config-${vmid}.lock"; |
d14a9a1b DM |
645 | } |
646 | ||
2d3f23be FG |
647 | sub lock_container { |
648 | my ($vmid, $timeout, $code, @param) = @_; | |
d14a9a1b DM |
649 | |
650 | $timeout = 10 if !$timeout; | |
d14a9a1b DM |
651 | |
652 | my $filename = lock_filename($vmid); | |
653 | ||
f99e8278 AD |
654 | mkdir $lockdir if !-d $lockdir; |
655 | ||
2d3f23be | 656 | my $res = PVE::Tools::lock_file_full($filename, $timeout, 0, $code, @param); |
f76a2828 | 657 | |
2d3f23be | 658 | die $@ if $@; |
f76a2828 DM |
659 | |
660 | return $res; | |
661 | } | |
662 | ||
ec52ac21 DM |
663 | sub option_exists { |
664 | my ($name) = @_; | |
665 | ||
666 | return defined($confdesc->{$name}); | |
667 | } | |
f76a2828 DM |
668 | |
669 | # add JSON properties for create and set function | |
670 | sub json_config_properties { | |
671 | my $prop = shift; | |
672 | ||
673 | foreach my $opt (keys %$confdesc) { | |
09d3ec42 | 674 | next if $opt eq 'parent' || $opt eq 'snaptime'; |
27916659 DM |
675 | next if $prop->{$opt}; |
676 | $prop->{$opt} = $confdesc->{$opt}; | |
677 | } | |
678 | ||
679 | return $prop; | |
680 | } | |
681 | ||
682 | sub json_config_properties_no_rootfs { | |
683 | my $prop = shift; | |
684 | ||
685 | foreach my $opt (keys %$confdesc) { | |
686 | next if $prop->{$opt}; | |
09d3ec42 | 687 | next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs'; |
f76a2828 DM |
688 | $prop->{$opt} = $confdesc->{$opt}; |
689 | } | |
690 | ||
691 | return $prop; | |
692 | } | |
693 | ||
822de0c3 DM |
694 | # container status helpers |
695 | ||
696 | sub list_active_containers { | |
cbb03fea | 697 | |
822de0c3 DM |
698 | my $filename = "/proc/net/unix"; |
699 | ||
700 | # similar test is used by lcxcontainers.c: list_active_containers | |
701 | my $res = {}; | |
cbb03fea | 702 | |
822de0c3 DM |
703 | my $fh = IO::File->new ($filename, "r"); |
704 | return $res if !$fh; | |
705 | ||
706 | while (defined(my $line = <$fh>)) { | |
707 | if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) { | |
708 | my $path = $1; | |
27916659 | 709 | if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) { |
822de0c3 DM |
710 | $res->{$1} = 1; |
711 | } | |
712 | } | |
713 | } | |
714 | ||
715 | close($fh); | |
cbb03fea | 716 | |
822de0c3 DM |
717 | return $res; |
718 | } | |
f76a2828 | 719 | |
5c752bbf DM |
720 | # warning: this is slow |
721 | sub check_running { | |
722 | my ($vmid) = @_; | |
723 | ||
724 | my $active_hash = list_active_containers(); | |
725 | ||
726 | return 1 if defined($active_hash->{$vmid}); | |
cbb03fea | 727 | |
5c752bbf DM |
728 | return undef; |
729 | } | |
730 | ||
10fc3ba5 | 731 | sub get_container_disk_usage { |
73e03cb7 | 732 | my ($vmid, $pid) = @_; |
10fc3ba5 | 733 | |
73e03cb7 | 734 | return PVE::Tools::df("/proc/$pid/root/", 1); |
10fc3ba5 DM |
735 | } |
736 | ||
688afc63 WL |
737 | my $last_proc_vmid_stat; |
738 | ||
739 | my $parse_cpuacct_stat = sub { | |
740 | my ($vmid) = @_; | |
741 | ||
742 | my $raw = read_cgroup_value('cpuacct', $vmid, 'cpuacct.stat', 1); | |
743 | ||
744 | my $stat = {}; | |
745 | ||
746 | if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) { | |
747 | ||
748 | $stat->{utime} = $1; | |
749 | $stat->{stime} = $2; | |
750 | ||
751 | } | |
752 | ||
753 | return $stat; | |
754 | }; | |
755 | ||
f76a2828 DM |
756 | sub vmstatus { |
757 | my ($opt_vmid) = @_; | |
758 | ||
759 | my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list(); | |
760 | ||
822de0c3 | 761 | my $active_hash = list_active_containers(); |
cbb03fea | 762 | |
688afc63 WL |
763 | my $cpucount = $cpuinfo->{cpus} || 1; |
764 | ||
765 | my $cdtime = gettimeofday; | |
766 | ||
767 | my $uptime = (PVE::ProcFSTools::read_proc_uptime(1))[0]; | |
768 | ||
f76a2828 | 769 | foreach my $vmid (keys %$list) { |
f76a2828 | 770 | my $d = $list->{$vmid}; |
10fc3ba5 | 771 | |
d5588ee3 DM |
772 | eval { $d->{pid} = find_lxc_pid($vmid) if defined($active_hash->{$vmid}); }; |
773 | warn $@ if $@; # ignore errors (consider them stopped) | |
cbb03fea | 774 | |
d5588ee3 | 775 | $d->{status} = $d->{pid} ? 'running' : 'stopped'; |
f76a2828 DM |
776 | |
777 | my $cfspath = cfs_config_path($vmid); | |
238a56cb | 778 | my $conf = PVE::Cluster::cfs_read_file($cfspath) || {}; |
cbb03fea | 779 | |
27916659 | 780 | $d->{name} = $conf->{'hostname'} || "CT$vmid"; |
238a56cb | 781 | $d->{name} =~ s/[\s]//g; |
cbb03fea | 782 | |
9db5687d | 783 | $d->{cpus} = $conf->{cpulimit} || $cpucount; |
44da0641 | 784 | |
d5588ee3 DM |
785 | if ($d->{pid}) { |
786 | my $res = get_container_disk_usage($vmid, $d->{pid}); | |
27916659 DM |
787 | $d->{disk} = $res->{used}; |
788 | $d->{maxdisk} = $res->{total}; | |
789 | } else { | |
790 | $d->{disk} = 0; | |
791 | # use 4GB by default ?? | |
792 | if (my $rootfs = $conf->{rootfs}) { | |
44a9face | 793 | my $rootinfo = parse_ct_rootfs($rootfs); |
27916659 DM |
794 | $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024; |
795 | } else { | |
796 | $d->{maxdisk} = 4*1024*1024*1024; | |
10fc3ba5 | 797 | } |
238a56cb | 798 | } |
cbb03fea | 799 | |
238a56cb DM |
800 | $d->{mem} = 0; |
801 | $d->{swap} = 0; | |
95df9a12 DM |
802 | $d->{maxmem} = ($conf->{memory}||512)*1024*1024; |
803 | $d->{maxswap} = ($conf->{swap}//0)*1024*1024; | |
e901d418 | 804 | |
238a56cb DM |
805 | $d->{uptime} = 0; |
806 | $d->{cpu} = 0; | |
e901d418 | 807 | |
238a56cb DM |
808 | $d->{netout} = 0; |
809 | $d->{netin} = 0; | |
f76a2828 | 810 | |
238a56cb DM |
811 | $d->{diskread} = 0; |
812 | $d->{diskwrite} = 0; | |
bb1ac2de DM |
813 | |
814 | $d->{template} = is_template($conf); | |
f76a2828 | 815 | } |
cbb03fea | 816 | |
238a56cb DM |
817 | foreach my $vmid (keys %$list) { |
818 | my $d = $list->{$vmid}; | |
d5588ee3 DM |
819 | my $pid = $d->{pid}; |
820 | ||
821 | next if !$pid; # skip stopped CTs | |
f76a2828 | 822 | |
88a8696b TL |
823 | my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime |
824 | $d->{uptime} = time - $ctime; # the method lxcfs uses | |
22a77285 | 825 | |
238a56cb DM |
826 | $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes'); |
827 | $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem}; | |
b5289322 AD |
828 | |
829 | my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1); | |
1e647c7c | 830 | my @bytes = split(/\n/, $blkio_bytes); |
b5289322 | 831 | foreach my $byte (@bytes) { |
1e647c7c DM |
832 | if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) { |
833 | $d->{diskread} = $2 if $key eq 'Read'; | |
834 | $d->{diskwrite} = $2 if $key eq 'Write'; | |
835 | } | |
b5289322 | 836 | } |
688afc63 WL |
837 | |
838 | my $pstat = &$parse_cpuacct_stat($vmid); | |
839 | ||
840 | my $used = $pstat->{utime} + $pstat->{stime}; | |
841 | ||
842 | my $old = $last_proc_vmid_stat->{$vmid}; | |
843 | if (!$old) { | |
844 | $last_proc_vmid_stat->{$vmid} = { | |
845 | time => $cdtime, | |
846 | used => $used, | |
847 | cpu => 0, | |
848 | }; | |
849 | next; | |
850 | } | |
851 | ||
852 | my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz}; | |
853 | ||
854 | if ($dtime > 1000) { | |
855 | my $dutime = $used - $old->{used}; | |
856 | ||
857 | $d->{cpu} = (($dutime/$dtime)* $cpucount) / $d->{cpus}; | |
858 | $last_proc_vmid_stat->{$vmid} = { | |
859 | time => $cdtime, | |
860 | used => $used, | |
861 | cpu => $d->{cpu}, | |
862 | }; | |
863 | } else { | |
864 | $d->{cpu} = $old->{cpu}; | |
865 | } | |
238a56cb | 866 | } |
cbb03fea | 867 | |
68b8f4d1 WL |
868 | my $netdev = PVE::ProcFSTools::read_proc_net_dev(); |
869 | ||
870 | foreach my $dev (keys %$netdev) { | |
871 | next if $dev !~ m/^veth([1-9]\d*)i/; | |
872 | my $vmid = $1; | |
873 | my $d = $list->{$vmid}; | |
874 | ||
875 | next if !$d; | |
876 | ||
877 | $d->{netout} += $netdev->{$dev}->{receive}; | |
878 | $d->{netin} += $netdev->{$dev}->{transmit}; | |
879 | ||
880 | } | |
881 | ||
f76a2828 DM |
882 | return $list; |
883 | } | |
884 | ||
7c921c80 WB |
885 | sub classify_mountpoint { |
886 | my ($vol) = @_; | |
887 | if ($vol =~ m!^/!) { | |
888 | return 'device' if $vol =~ m!^/dev/!; | |
889 | return 'bind'; | |
890 | } | |
891 | return 'volume'; | |
892 | } | |
893 | ||
44a9face DM |
894 | my $parse_ct_mountpoint_full = sub { |
895 | my ($desc, $data, $noerr) = @_; | |
27916659 DM |
896 | |
897 | $data //= ''; | |
898 | ||
1b2c1e8c | 899 | my $res; |
44a9face | 900 | eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) }; |
1b2c1e8c | 901 | if ($@) { |
ca7feb1a WB |
902 | return undef if $noerr; |
903 | die $@; | |
27916659 DM |
904 | } |
905 | ||
bf4a209a | 906 | if (defined(my $size = $res->{size})) { |
ca7feb1a WB |
907 | $size = PVE::JSONSchema::parse_size($size); |
908 | if (!defined($size)) { | |
909 | return undef if $noerr; | |
910 | die "invalid size: $size\n"; | |
911 | } | |
912 | $res->{size} = $size; | |
27916659 DM |
913 | } |
914 | ||
7c921c80 WB |
915 | $res->{type} = classify_mountpoint($res->{volume}); |
916 | ||
27916659 | 917 | return $res; |
44a9face DM |
918 | }; |
919 | ||
920 | sub parse_ct_rootfs { | |
921 | my ($data, $noerr) = @_; | |
922 | ||
923 | my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr); | |
924 | ||
925 | $res->{mp} = '/' if defined($res); | |
926 | ||
927 | return $res; | |
928 | } | |
929 | ||
930 | sub parse_ct_mountpoint { | |
931 | my ($data, $noerr) = @_; | |
932 | ||
933 | return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr); | |
27916659 | 934 | } |
7dfc49cc | 935 | |
dde7b02b | 936 | sub print_ct_mountpoint { |
4fee75fd | 937 | my ($info, $nomp) = @_; |
7c921c80 WB |
938 | my $skip = [ 'type' ]; |
939 | push @$skip, 'mp' if $nomp; | |
6708ba93 | 940 | return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip); |
bb1ac2de DM |
941 | } |
942 | ||
7dfc49cc | 943 | sub print_lxc_network { |
f76a2828 | 944 | my $net = shift; |
6708ba93 | 945 | return PVE::JSONSchema::print_property_string($net, $netconf_desc); |
f76a2828 DM |
946 | } |
947 | ||
7dfc49cc DM |
948 | sub parse_lxc_network { |
949 | my ($data) = @_; | |
950 | ||
951 | my $res = {}; | |
952 | ||
953 | return $res if !$data; | |
954 | ||
ca7feb1a | 955 | $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data); |
7dfc49cc DM |
956 | |
957 | $res->{type} = 'veth'; | |
93cdbbfb | 958 | $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr}; |
cbb03fea | 959 | |
7dfc49cc DM |
960 | return $res; |
961 | } | |
f76a2828 | 962 | |
238a56cb DM |
963 | sub read_cgroup_value { |
964 | my ($group, $vmid, $name, $full) = @_; | |
965 | ||
966 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
967 | ||
968 | return PVE::Tools::file_get_contents($path) if $full; | |
969 | ||
970 | return PVE::Tools::file_read_firstline($path); | |
971 | } | |
972 | ||
bf0b8c43 AD |
973 | sub write_cgroup_value { |
974 | my ($group, $vmid, $name, $value) = @_; | |
975 | ||
976 | my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name"; | |
977 | PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path; | |
978 | ||
979 | } | |
980 | ||
52f1d76b DM |
981 | sub find_lxc_console_pids { |
982 | ||
983 | my $res = {}; | |
984 | ||
985 | PVE::Tools::dir_glob_foreach('/proc', '\d+', sub { | |
986 | my ($pid) = @_; | |
987 | ||
988 | my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline"); | |
989 | return if !$cmdline; | |
990 | ||
991 | my @args = split(/\0/, $cmdline); | |
992 | ||
c31ad455 | 993 | # search for lxc-console -n <vmid> |
cbb03fea | 994 | return if scalar(@args) != 3; |
52f1d76b DM |
995 | return if $args[1] ne '-n'; |
996 | return if $args[2] !~ m/^\d+$/; | |
997 | return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|; | |
cbb03fea | 998 | |
52f1d76b | 999 | my $vmid = $args[2]; |
cbb03fea | 1000 | |
52f1d76b DM |
1001 | push @{$res->{$vmid}}, $pid; |
1002 | }); | |
1003 | ||
1004 | return $res; | |
1005 | } | |
1006 | ||
bedeaaf1 AD |
1007 | sub find_lxc_pid { |
1008 | my ($vmid) = @_; | |
1009 | ||
1010 | my $pid = undef; | |
1011 | my $parser = sub { | |
1012 | my $line = shift; | |
8b25977f | 1013 | $pid = $1 if $line =~ m/^PID:\s+(\d+)$/; |
bedeaaf1 | 1014 | }; |
c39aa40a | 1015 | PVE::Tools::run_command(['lxc-info', '-n', $vmid, '-p'], outfunc => $parser); |
bedeaaf1 | 1016 | |
8b25977f | 1017 | die "unable to get PID for CT $vmid (not running?)\n" if !$pid; |
cbb03fea | 1018 | |
8b25977f | 1019 | return $pid; |
bedeaaf1 AD |
1020 | } |
1021 | ||
cbb03fea | 1022 | # Note: we cannot use Net:IP, because that only allows strict |
55fa4e09 DM |
1023 | # CIDR networks |
1024 | sub parse_ipv4_cidr { | |
1025 | my ($cidr, $noerr) = @_; | |
1026 | ||
f7a7b413 WB |
1027 | if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) { |
1028 | return { address => $1, netmask => $PVE::Network::ipv4_reverse_mask->[$2] }; | |
55fa4e09 | 1029 | } |
cbb03fea | 1030 | |
55fa4e09 | 1031 | return undef if $noerr; |
cbb03fea | 1032 | |
55fa4e09 DM |
1033 | die "unable to parse ipv4 address/mask\n"; |
1034 | } | |
93285df8 | 1035 | |
a12a36e0 WL |
1036 | sub check_lock { |
1037 | my ($conf) = @_; | |
1038 | ||
27916659 | 1039 | die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'}; |
a12a36e0 WL |
1040 | } |
1041 | ||
e22af68f AG |
1042 | sub check_protection { |
1043 | my ($vm_conf, $err_msg) = @_; | |
1044 | ||
1045 | if ($vm_conf->{protection}) { | |
1046 | die "$err_msg - protection mode enabled\n"; | |
1047 | } | |
1048 | } | |
1049 | ||
27916659 | 1050 | sub update_lxc_config { |
c628ffa1 | 1051 | my ($storage_cfg, $vmid, $conf) = @_; |
b80dd50a | 1052 | |
bb1ac2de DM |
1053 | my $dir = "/var/lib/lxc/$vmid"; |
1054 | ||
1055 | if ($conf->{template}) { | |
1056 | ||
1057 | unlink "$dir/config"; | |
1058 | ||
1059 | return; | |
1060 | } | |
1061 | ||
27916659 | 1062 | my $raw = ''; |
b80dd50a | 1063 | |
27916659 DM |
1064 | die "missing 'arch' - internal error" if !$conf->{arch}; |
1065 | $raw .= "lxc.arch = $conf->{arch}\n"; | |
b80dd50a | 1066 | |
425b62cb WB |
1067 | my $unprivileged = $conf->{unprivileged}; |
1068 | my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}}; | |
1069 | ||
27916659 | 1070 | my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error"; |
fa7cb12b | 1071 | if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux)$/x) { |
27916659 | 1072 | $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n"; |
425b62cb WB |
1073 | if ($unprivileged || $custom_idmap) { |
1074 | $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n" | |
1075 | } | |
27916659 | 1076 | } else { |
9a7a910b | 1077 | die "implement me (ostype $ostype)"; |
27916659 | 1078 | } |
b80dd50a | 1079 | |
c16b8890 | 1080 | $raw .= "lxc.monitor.unshare = 1\n"; |
58cc92a9 | 1081 | |
425b62cb WB |
1082 | # Should we read them from /etc/subuid? |
1083 | if ($unprivileged && !$custom_idmap) { | |
1084 | $raw .= "lxc.id_map = u 0 100000 65536\n"; | |
1085 | $raw .= "lxc.id_map = g 0 100000 65536\n"; | |
1086 | } | |
1087 | ||
6f035afe | 1088 | if (!has_dev_console($conf)) { |
eeaea429 DM |
1089 | $raw .= "lxc.console = none\n"; |
1090 | $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n"; | |
1091 | } | |
4f958489 | 1092 | |
0d0ca400 | 1093 | my $ttycount = get_tty_count($conf); |
27916659 | 1094 | $raw .= "lxc.tty = $ttycount\n"; |
cbb03fea | 1095 | |
c31ad455 | 1096 | # some init scripts expect a linux terminal (turnkey). |
a691a5a3 DM |
1097 | $raw .= "lxc.environment = TERM=linux\n"; |
1098 | ||
27916659 DM |
1099 | my $utsname = $conf->{hostname} || "CT$vmid"; |
1100 | $raw .= "lxc.utsname = $utsname\n"; | |
cbb03fea | 1101 | |
27916659 DM |
1102 | my $memory = $conf->{memory} || 512; |
1103 | my $swap = $conf->{swap} // 0; | |
1104 | ||
1105 | my $lxcmem = int($memory*1024*1024); | |
1106 | $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n"; | |
a12a36e0 | 1107 | |
27916659 DM |
1108 | my $lxcswap = int(($memory + $swap)*1024*1024); |
1109 | $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n"; | |
1110 | ||
1111 | if (my $cpulimit = $conf->{cpulimit}) { | |
1112 | $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n"; | |
1113 | my $value = int(100000*$cpulimit); | |
1114 | $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n"; | |
a12a36e0 WL |
1115 | } |
1116 | ||
27916659 DM |
1117 | my $shares = $conf->{cpuunits} || 1024; |
1118 | $raw .= "lxc.cgroup.cpu.shares = $shares\n"; | |
1119 | ||
44a9face | 1120 | my $mountpoint = parse_ct_rootfs($conf->{rootfs}); |
a3076d81 | 1121 | |
c9a5774b | 1122 | $raw .= "lxc.rootfs = $dir/rootfs\n"; |
27916659 DM |
1123 | |
1124 | my $netcount = 0; | |
1125 | foreach my $k (keys %$conf) { | |
1126 | next if $k !~ m/^net(\d+)$/; | |
1127 | my $ind = $1; | |
a16d94c8 | 1128 | my $d = parse_lxc_network($conf->{$k}); |
27916659 DM |
1129 | $netcount++; |
1130 | $raw .= "lxc.network.type = veth\n"; | |
18862537 | 1131 | $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n"; |
27916659 DM |
1132 | $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr}); |
1133 | $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name}); | |
1134 | $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu}); | |
a12a36e0 WL |
1135 | } |
1136 | ||
e576f689 DM |
1137 | if (my $lxcconf = $conf->{lxc}) { |
1138 | foreach my $entry (@$lxcconf) { | |
1139 | my ($k, $v) = @$entry; | |
1140 | $netcount++ if $k eq 'lxc.network.type'; | |
1141 | $raw .= "$k = $v\n"; | |
1142 | } | |
1143 | } | |
27916659 | 1144 | |
e576f689 DM |
1145 | $raw .= "lxc.network.type = empty\n" if !$netcount; |
1146 | ||
27916659 DM |
1147 | File::Path::mkpath("$dir/rootfs"); |
1148 | ||
1149 | PVE::Tools::file_set_contents("$dir/config", $raw); | |
b80dd50a DM |
1150 | } |
1151 | ||
117636e5 DM |
1152 | # verify and cleanup nameserver list (replace \0 with ' ') |
1153 | sub verify_nameserver_list { | |
1154 | my ($nameserver_list) = @_; | |
1155 | ||
1156 | my @list = (); | |
1157 | foreach my $server (PVE::Tools::split_list($nameserver_list)) { | |
1158 | PVE::JSONSchema::pve_verify_ip($server); | |
1159 | push @list, $server; | |
1160 | } | |
1161 | ||
1162 | return join(' ', @list); | |
1163 | } | |
1164 | ||
1165 | sub verify_searchdomain_list { | |
1166 | my ($searchdomain_list) = @_; | |
1167 | ||
1168 | my @list = (); | |
1169 | foreach my $server (PVE::Tools::split_list($searchdomain_list)) { | |
1170 | # todo: should we add checks for valid dns domains? | |
1171 | push @list, $server; | |
1172 | } | |
1173 | ||
1174 | return join(' ', @list); | |
1175 | } | |
1176 | ||
69202f71 WB |
1177 | sub add_unused_volume { |
1178 | my ($config, $volid) = @_; | |
1179 | ||
1180 | my $key; | |
1181 | for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) { | |
1182 | my $test = "unused$ind"; | |
1183 | if (my $vid = $config->{$test}) { | |
1184 | return if $vid eq $volid; # do not add duplicates | |
1185 | } else { | |
1186 | $key = $test; | |
1187 | } | |
1188 | } | |
1189 | ||
c31ad455 | 1190 | die "Too many unused volumes - please delete them first.\n" if !$key; |
69202f71 WB |
1191 | |
1192 | $config->{$key} = $volid; | |
1193 | ||
1194 | return $key; | |
1195 | } | |
1196 | ||
27916659 | 1197 | sub update_pct_config { |
93285df8 DM |
1198 | my ($vmid, $conf, $running, $param, $delete) = @_; |
1199 | ||
bf0b8c43 AD |
1200 | my @nohotplug; |
1201 | ||
7b49dfe0 | 1202 | my $new_disks = 0; |
69202f71 | 1203 | my @deleted_volumes; |
4fee75fd | 1204 | |
cbb03fea DM |
1205 | my $rootdir; |
1206 | if ($running) { | |
bedeaaf1 | 1207 | my $pid = find_lxc_pid($vmid); |
cbb03fea | 1208 | $rootdir = "/proc/$pid/root"; |
bedeaaf1 AD |
1209 | } |
1210 | ||
7a168607 DM |
1211 | my $hotplug_error = sub { |
1212 | if ($running) { | |
a6a77cfa WB |
1213 | push @nohotplug, @_; |
1214 | return 1; | |
7a168607 DM |
1215 | } else { |
1216 | return 0; | |
a6a77cfa | 1217 | } |
7a168607 | 1218 | }; |
a6a77cfa | 1219 | |
93285df8 DM |
1220 | if (defined($delete)) { |
1221 | foreach my $opt (@$delete) { | |
a61a5448 WB |
1222 | if (!exists($conf->{$opt})) { |
1223 | warn "no such option: $opt\n"; | |
1224 | next; | |
1225 | } | |
1226 | ||
27916659 | 1227 | if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') { |
93285df8 DM |
1228 | die "unable to delete required option '$opt'\n"; |
1229 | } elsif ($opt eq 'swap') { | |
27916659 | 1230 | delete $conf->{$opt}; |
bf0b8c43 | 1231 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1); |
40603eb3 | 1232 | } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') { |
27916659 | 1233 | delete $conf->{$opt}; |
4f958489 | 1234 | } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' || |
40603eb3 | 1235 | $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') { |
a6a77cfa | 1236 | next if $hotplug_error->($opt); |
27916659 | 1237 | delete $conf->{$opt}; |
68fba17b | 1238 | } elsif ($opt =~ m/^net(\d)$/) { |
93285df8 | 1239 | delete $conf->{$opt}; |
68fba17b AD |
1240 | next if !$running; |
1241 | my $netid = $1; | |
18862537 | 1242 | PVE::Network::veth_delete("veth${vmid}i$netid"); |
7e806596 AG |
1243 | } elsif ($opt eq 'protection') { |
1244 | delete $conf->{$opt}; | |
69202f71 | 1245 | } elsif ($opt =~ m/^unused(\d+)$/) { |
a6a77cfa | 1246 | next if $hotplug_error->($opt); |
69202f71 WB |
1247 | check_protection($conf, "can't remove CT $vmid drive '$opt'"); |
1248 | push @deleted_volumes, $conf->{$opt}; | |
1249 | delete $conf->{$opt}; | |
4fee75fd | 1250 | } elsif ($opt =~ m/^mp(\d+)$/) { |
a6a77cfa | 1251 | next if $hotplug_error->($opt); |
e22af68f | 1252 | check_protection($conf, "can't remove CT $vmid drive '$opt'"); |
69202f71 | 1253 | my $mountpoint = parse_ct_mountpoint($conf->{$opt}); |
7c921c80 WB |
1254 | if ($mountpoint->{type} eq 'volume') { |
1255 | add_unused_volume($conf, $mountpoint->{volume}) | |
1256 | } | |
4fee75fd | 1257 | delete $conf->{$opt}; |
425b62cb WB |
1258 | } elsif ($opt eq 'unprivileged') { |
1259 | die "unable to delete read-only option: '$opt'\n"; | |
93285df8 | 1260 | } else { |
9a7a910b | 1261 | die "implement me (delete: $opt)" |
93285df8 | 1262 | } |
706c9791 | 1263 | write_config($vmid, $conf) if $running; |
93285df8 DM |
1264 | } |
1265 | } | |
1266 | ||
be6383d7 WB |
1267 | # There's no separate swap size to configure, there's memory and "total" |
1268 | # memory (iow. memory+swap). This means we have to change them together. | |
27916659 DM |
1269 | my $wanted_memory = PVE::Tools::extract_param($param, 'memory'); |
1270 | my $wanted_swap = PVE::Tools::extract_param($param, 'swap'); | |
be6383d7 | 1271 | if (defined($wanted_memory) || defined($wanted_swap)) { |
27916659 | 1272 | |
a2c57b0c WB |
1273 | my $old_memory = ($conf->{memory} || 512); |
1274 | my $old_swap = ($conf->{swap} || 0); | |
1275 | ||
1276 | $wanted_memory //= $old_memory; | |
1277 | $wanted_swap //= $old_swap; | |
27916659 DM |
1278 | |
1279 | my $total = $wanted_memory + $wanted_swap; | |
1280 | if ($running) { | |
a2c57b0c WB |
1281 | my $old_total = $old_memory + $old_swap; |
1282 | if ($total > $old_total) { | |
1283 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024)); | |
1284 | write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024)); | |
1285 | } else { | |
1286 | write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024)); | |
1287 | write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024)); | |
1288 | } | |
be6383d7 | 1289 | } |
27916659 DM |
1290 | $conf->{memory} = $wanted_memory; |
1291 | $conf->{swap} = $wanted_swap; | |
1292 | ||
706c9791 | 1293 | write_config($vmid, $conf) if $running; |
be6383d7 WB |
1294 | } |
1295 | ||
93285df8 DM |
1296 | foreach my $opt (keys %$param) { |
1297 | my $value = $param->{$opt}; | |
1298 | if ($opt eq 'hostname') { | |
27916659 | 1299 | $conf->{$opt} = $value; |
a99b3509 | 1300 | } elsif ($opt eq 'onboot') { |
27916659 | 1301 | $conf->{$opt} = $value ? 1 : 0; |
a3249355 | 1302 | } elsif ($opt eq 'startup') { |
27916659 | 1303 | $conf->{$opt} = $value; |
40603eb3 | 1304 | } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') { |
a6a77cfa | 1305 | next if $hotplug_error->($opt); |
e576f689 | 1306 | $conf->{$opt} = $value; |
ffa1d001 | 1307 | } elsif ($opt eq 'nameserver') { |
a6a77cfa | 1308 | next if $hotplug_error->($opt); |
117636e5 | 1309 | my $list = verify_nameserver_list($value); |
27916659 | 1310 | $conf->{$opt} = $list; |
ffa1d001 | 1311 | } elsif ($opt eq 'searchdomain') { |
a6a77cfa | 1312 | next if $hotplug_error->($opt); |
117636e5 | 1313 | my $list = verify_searchdomain_list($value); |
27916659 | 1314 | $conf->{$opt} = $list; |
45573f7c | 1315 | } elsif ($opt eq 'cpulimit') { |
a6a77cfa | 1316 | next if $hotplug_error->($opt); # FIXME: hotplug |
27916659 | 1317 | $conf->{$opt} = $value; |
b80dd50a | 1318 | } elsif ($opt eq 'cpuunits') { |
27916659 | 1319 | $conf->{$opt} = $value; |
bf0b8c43 | 1320 | write_cgroup_value("cpu", $vmid, "cpu.shares", $value); |
93285df8 | 1321 | } elsif ($opt eq 'description') { |
27916659 | 1322 | $conf->{$opt} = PVE::Tools::encode_text($value); |
93285df8 DM |
1323 | } elsif ($opt =~ m/^net(\d+)$/) { |
1324 | my $netid = $1; | |
a16d94c8 | 1325 | my $net = parse_lxc_network($value); |
27916659 DM |
1326 | if (!$running) { |
1327 | $conf->{$opt} = print_lxc_network($net); | |
cbb03fea | 1328 | } else { |
bedeaaf1 AD |
1329 | update_net($vmid, $conf, $opt, $net, $netid, $rootdir); |
1330 | } | |
7e806596 AG |
1331 | } elsif ($opt eq 'protection') { |
1332 | $conf->{$opt} = $value ? 1 : 0; | |
4fee75fd | 1333 | } elsif ($opt =~ m/^mp(\d+)$/) { |
a6a77cfa | 1334 | next if $hotplug_error->($opt); |
e22af68f | 1335 | check_protection($conf, "can't update CT $vmid drive '$opt'"); |
4fee75fd | 1336 | $conf->{$opt} = $value; |
7b49dfe0 | 1337 | $new_disks = 1; |
4fee75fd | 1338 | } elsif ($opt eq 'rootfs') { |
e22af68f | 1339 | check_protection($conf, "can't update CT $vmid drive '$opt'"); |
b51a98d4 | 1340 | die "implement me: $opt"; |
425b62cb WB |
1341 | } elsif ($opt eq 'unprivileged') { |
1342 | die "unable to modify read-only option: '$opt'\n"; | |
93285df8 | 1343 | } else { |
a92f66c9 | 1344 | die "implement me: $opt"; |
93285df8 | 1345 | } |
706c9791 | 1346 | write_config($vmid, $conf) if $running; |
93285df8 | 1347 | } |
bf0b8c43 | 1348 | |
69202f71 WB |
1349 | if (@deleted_volumes) { |
1350 | my $storage_cfg = PVE::Storage::config(); | |
1351 | foreach my $volume (@deleted_volumes) { | |
1352 | delete_mountpoint_volume($storage_cfg, $vmid, $volume); | |
1353 | } | |
1354 | } | |
1355 | ||
7b49dfe0 | 1356 | if ($new_disks) { |
4fee75fd | 1357 | my $storage_cfg = PVE::Storage::config(); |
6c871c36 | 1358 | create_disks($storage_cfg, $vmid, $conf, $conf); |
4fee75fd | 1359 | } |
694c25df WB |
1360 | |
1361 | # This should be the last thing we do here | |
1362 | if ($running && scalar(@nohotplug)) { | |
1363 | die "unable to modify " . join(',', @nohotplug) . " while container is running\n"; | |
1364 | } | |
93285df8 | 1365 | } |
c325b32f | 1366 | |
6f035afe DM |
1367 | sub has_dev_console { |
1368 | my ($conf) = @_; | |
1369 | ||
1370 | return !(defined($conf->{console}) && !$conf->{console}); | |
1371 | } | |
1372 | ||
0d0ca400 DM |
1373 | sub get_tty_count { |
1374 | my ($conf) = @_; | |
1375 | ||
1376 | return $conf->{tty} // $confdesc->{tty}->{default}; | |
1377 | } | |
1378 | ||
aca816ad DM |
1379 | sub get_cmode { |
1380 | my ($conf) = @_; | |
1381 | ||
1382 | return $conf->{cmode} // $confdesc->{cmode}->{default}; | |
1383 | } | |
1384 | ||
1385 | sub get_console_command { | |
1386 | my ($vmid, $conf) = @_; | |
1387 | ||
1388 | my $cmode = get_cmode($conf); | |
1389 | ||
1390 | if ($cmode eq 'console') { | |
1391 | return ['lxc-console', '-n', $vmid, '-t', 0]; | |
1392 | } elsif ($cmode eq 'tty') { | |
1393 | return ['lxc-console', '-n', $vmid]; | |
1394 | } elsif ($cmode eq 'shell') { | |
1395 | return ['lxc-attach', '--clear-env', '-n', $vmid]; | |
1396 | } else { | |
1397 | die "internal error"; | |
1398 | } | |
1399 | } | |
1400 | ||
c325b32f DM |
1401 | sub get_primary_ips { |
1402 | my ($conf) = @_; | |
1403 | ||
1404 | # return data from net0 | |
cbb03fea | 1405 | |
27916659 | 1406 | return undef if !defined($conf->{net0}); |
a16d94c8 | 1407 | my $net = parse_lxc_network($conf->{net0}); |
c325b32f DM |
1408 | |
1409 | my $ipv4 = $net->{ip}; | |
db78a181 WB |
1410 | if ($ipv4) { |
1411 | if ($ipv4 =~ /^(dhcp|manual)$/) { | |
1412 | $ipv4 = undef | |
1413 | } else { | |
1414 | $ipv4 =~ s!/\d+$!!; | |
1415 | } | |
1416 | } | |
65e5eaa3 | 1417 | my $ipv6 = $net->{ip6}; |
db78a181 | 1418 | if ($ipv6) { |
5f291c7d | 1419 | if ($ipv6 =~ /^(auto|dhcp|manual)$/) { |
db78a181 WB |
1420 | $ipv6 = undef; |
1421 | } else { | |
1422 | $ipv6 =~ s!/\d+$!!; | |
1423 | } | |
1424 | } | |
cbb03fea | 1425 | |
c325b32f DM |
1426 | return ($ipv4, $ipv6); |
1427 | } | |
148d1cb4 | 1428 | |
b407293b WB |
1429 | sub delete_mountpoint_volume { |
1430 | my ($storage_cfg, $vmid, $volume) = @_; | |
1431 | ||
7c921c80 | 1432 | return if classify_mountpoint($volume) ne 'volume'; |
b407293b WB |
1433 | |
1434 | my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume); | |
1435 | PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner; | |
1436 | } | |
ef241384 | 1437 | |
27916659 | 1438 | sub destroy_lxc_container { |
148d1cb4 DM |
1439 | my ($storage_cfg, $vmid, $conf) = @_; |
1440 | ||
db8989e1 WB |
1441 | foreach_mountpoint($conf, sub { |
1442 | my ($ms, $mountpoint) = @_; | |
b407293b | 1443 | delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume}); |
db8989e1 WB |
1444 | }); |
1445 | ||
27916659 DM |
1446 | rmdir "/var/lib/lxc/$vmid/rootfs"; |
1447 | unlink "/var/lib/lxc/$vmid/config"; | |
1448 | rmdir "/var/lib/lxc/$vmid"; | |
1449 | destroy_config($vmid); | |
1450 | ||
1451 | #my $cmd = ['lxc-destroy', '-n', $vmid ]; | |
1452 | #PVE::Tools::run_command($cmd); | |
148d1cb4 | 1453 | } |
68fba17b | 1454 | |
ef241384 | 1455 | sub vm_stop_cleanup { |
5fa890f0 | 1456 | my ($storage_cfg, $vmid, $conf, $keepActive) = @_; |
ef241384 DM |
1457 | |
1458 | eval { | |
1459 | if (!$keepActive) { | |
bf9d912c | 1460 | |
09aa32fd | 1461 | my $vollist = get_vm_volumes($conf); |
a8b6b8a7 | 1462 | PVE::Storage::deactivate_volumes($storage_cfg, $vollist); |
ef241384 DM |
1463 | } |
1464 | }; | |
1465 | warn $@ if $@; # avoid errors - just warn | |
1466 | } | |
1467 | ||
93cdbbfb AD |
1468 | my $safe_num_ne = sub { |
1469 | my ($a, $b) = @_; | |
1470 | ||
1471 | return 0 if !defined($a) && !defined($b); | |
1472 | return 1 if !defined($a); | |
1473 | return 1 if !defined($b); | |
1474 | ||
1475 | return $a != $b; | |
1476 | }; | |
1477 | ||
1478 | my $safe_string_ne = sub { | |
1479 | my ($a, $b) = @_; | |
1480 | ||
1481 | return 0 if !defined($a) && !defined($b); | |
1482 | return 1 if !defined($a); | |
1483 | return 1 if !defined($b); | |
1484 | ||
1485 | return $a ne $b; | |
1486 | }; | |
1487 | ||
1488 | sub update_net { | |
bedeaaf1 | 1489 | my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_; |
93cdbbfb | 1490 | |
18862537 WB |
1491 | if ($newnet->{type} ne 'veth') { |
1492 | # for when there are physical interfaces | |
1493 | die "cannot update interface of type $newnet->{type}"; | |
1494 | } | |
1495 | ||
1496 | my $veth = "veth${vmid}i${netid}"; | |
93cdbbfb AD |
1497 | my $eth = $newnet->{name}; |
1498 | ||
18862537 WB |
1499 | if (my $oldnetcfg = $conf->{$opt}) { |
1500 | my $oldnet = parse_lxc_network($oldnetcfg); | |
1501 | ||
1502 | if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) || | |
1503 | &$safe_string_ne($oldnet->{name}, $newnet->{name})) { | |
93cdbbfb | 1504 | |
18862537 | 1505 | PVE::Network::veth_delete($veth); |
bedeaaf1 | 1506 | delete $conf->{$opt}; |
706c9791 | 1507 | write_config($vmid, $conf); |
93cdbbfb | 1508 | |
18862537 | 1509 | hotplug_net($vmid, $conf, $opt, $newnet, $netid); |
bedeaaf1 | 1510 | |
18862537 WB |
1511 | } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) || |
1512 | &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) || | |
1513 | &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) { | |
bedeaaf1 | 1514 | |
18862537 | 1515 | if ($oldnet->{bridge}) { |
bedeaaf1 | 1516 | PVE::Network::tap_unplug($veth); |
18862537 WB |
1517 | foreach (qw(bridge tag firewall)) { |
1518 | delete $oldnet->{$_}; | |
1519 | } | |
1520 | $conf->{$opt} = print_lxc_network($oldnet); | |
706c9791 | 1521 | write_config($vmid, $conf); |
bedeaaf1 | 1522 | } |
93cdbbfb | 1523 | |
23eb2244 | 1524 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}); |
18862537 WB |
1525 | foreach (qw(bridge tag firewall)) { |
1526 | $oldnet->{$_} = $newnet->{$_} if $newnet->{$_}; | |
1527 | } | |
1528 | $conf->{$opt} = print_lxc_network($oldnet); | |
706c9791 | 1529 | write_config($vmid, $conf); |
93cdbbfb AD |
1530 | } |
1531 | } else { | |
18862537 | 1532 | hotplug_net($vmid, $conf, $opt, $newnet, $netid); |
93cdbbfb AD |
1533 | } |
1534 | ||
bedeaaf1 | 1535 | update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir); |
93cdbbfb AD |
1536 | } |
1537 | ||
1538 | sub hotplug_net { | |
18862537 | 1539 | my ($vmid, $conf, $opt, $newnet, $netid) = @_; |
93cdbbfb | 1540 | |
18862537 | 1541 | my $veth = "veth${vmid}i${netid}"; |
cbb03fea | 1542 | my $vethpeer = $veth . "p"; |
93cdbbfb AD |
1543 | my $eth = $newnet->{name}; |
1544 | ||
1545 | PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr}); | |
23eb2244 | 1546 | PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall}, $newnet->{trunks}); |
93cdbbfb | 1547 | |
cbb03fea | 1548 | # attach peer in container |
93cdbbfb AD |
1549 | my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ]; |
1550 | PVE::Tools::run_command($cmd); | |
1551 | ||
cbb03fea | 1552 | # link up peer in container |
93cdbbfb AD |
1553 | $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ]; |
1554 | PVE::Tools::run_command($cmd); | |
bedeaaf1 | 1555 | |
18862537 WB |
1556 | my $done = { type => 'veth' }; |
1557 | foreach (qw(bridge tag firewall hwaddr name)) { | |
1558 | $done->{$_} = $newnet->{$_} if $newnet->{$_}; | |
1559 | } | |
1560 | $conf->{$opt} = print_lxc_network($done); | |
bedeaaf1 | 1561 | |
706c9791 | 1562 | write_config($vmid, $conf); |
93cdbbfb AD |
1563 | } |
1564 | ||
68a05bb3 | 1565 | sub update_ipconfig { |
bedeaaf1 AD |
1566 | my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_; |
1567 | ||
f2104b80 | 1568 | my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); |
bedeaaf1 | 1569 | |
18862537 | 1570 | my $optdata = parse_lxc_network($conf->{$opt}); |
84e0c123 WB |
1571 | my $deleted = []; |
1572 | my $added = []; | |
8d723477 WB |
1573 | my $nscmd = sub { |
1574 | my $cmdargs = shift; | |
1575 | PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs); | |
84e0c123 | 1576 | }; |
8d723477 | 1577 | my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) }; |
2bfd1615 | 1578 | |
84e0c123 | 1579 | my $change_ip_config = sub { |
f39002a6 DM |
1580 | my ($ipversion) = @_; |
1581 | ||
1582 | my $family_opt = "-$ipversion"; | |
1583 | my $suffix = $ipversion == 4 ? '' : $ipversion; | |
84e0c123 WB |
1584 | my $gw= "gw$suffix"; |
1585 | my $ip= "ip$suffix"; | |
bedeaaf1 | 1586 | |
6178b0dd WB |
1587 | my $newip = $newnet->{$ip}; |
1588 | my $newgw = $newnet->{$gw}; | |
1589 | my $oldip = $optdata->{$ip}; | |
1590 | ||
1591 | my $change_ip = &$safe_string_ne($oldip, $newip); | |
1592 | my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw); | |
bedeaaf1 | 1593 | |
84e0c123 | 1594 | return if !$change_ip && !$change_gw; |
68a05bb3 | 1595 | |
84e0c123 | 1596 | # step 1: add new IP, if this fails we cancel |
292aff54 WB |
1597 | my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/); |
1598 | if ($change_ip && $is_real_ip) { | |
8d723477 | 1599 | eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); }; |
84e0c123 WB |
1600 | if (my $err = $@) { |
1601 | warn $err; | |
1602 | return; | |
1603 | } | |
bedeaaf1 | 1604 | } |
bedeaaf1 | 1605 | |
84e0c123 WB |
1606 | # step 2: replace gateway |
1607 | # If this fails we delete the added IP and cancel. | |
1608 | # If it succeeds we save the config and delete the old IP, ignoring | |
1609 | # errors. The config is then saved. | |
1610 | # Note: 'ip route replace' can add | |
1611 | if ($change_gw) { | |
6178b0dd | 1612 | if ($newgw) { |
292aff54 WB |
1613 | eval { |
1614 | if ($is_real_ip && !PVE::Network::is_ip_in_cidr($newgw, $newip, $ipversion)) { | |
1615 | &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth); | |
1616 | } | |
1617 | &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); | |
1618 | }; | |
84e0c123 WB |
1619 | if (my $err = $@) { |
1620 | warn $err; | |
1621 | # the route was not replaced, the old IP is still available | |
1622 | # rollback (delete new IP) and cancel | |
1623 | if ($change_ip) { | |
8d723477 | 1624 | eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); }; |
84e0c123 WB |
1625 | warn $@ if $@; # no need to die here |
1626 | } | |
1627 | return; | |
1628 | } | |
1629 | } else { | |
8d723477 | 1630 | eval { &$ipcmd($family_opt, 'route', 'del', 'default'); }; |
84e0c123 WB |
1631 | # if the route was not deleted, the guest might have deleted it manually |
1632 | # warn and continue | |
1633 | warn $@ if $@; | |
1634 | } | |
2bfd1615 | 1635 | } |
2bfd1615 | 1636 | |
6178b0dd | 1637 | # from this point on we save the configuration |
84e0c123 | 1638 | # step 3: delete old IP ignoring errors |
6178b0dd | 1639 | if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) { |
8d723477 WB |
1640 | # We need to enable promote_secondaries, otherwise our newly added |
1641 | # address will be removed along with the old one. | |
1642 | my $promote = 0; | |
1643 | eval { | |
1644 | if ($ipversion == 4) { | |
1645 | &$nscmd({ outfunc => sub { $promote = int(shift) } }, | |
1646 | 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries"); | |
1647 | &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1"); | |
1648 | } | |
1649 | &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth); | |
1650 | }; | |
84e0c123 | 1651 | warn $@ if $@; # no need to die here |
8d723477 WB |
1652 | |
1653 | if ($ipversion == 4) { | |
1654 | &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote"); | |
1655 | } | |
bedeaaf1 AD |
1656 | } |
1657 | ||
84e0c123 WB |
1658 | foreach my $property ($ip, $gw) { |
1659 | if ($newnet->{$property}) { | |
1660 | $optdata->{$property} = $newnet->{$property}; | |
1661 | } else { | |
1662 | delete $optdata->{$property}; | |
1663 | } | |
bedeaaf1 | 1664 | } |
18862537 | 1665 | $conf->{$opt} = print_lxc_network($optdata); |
706c9791 | 1666 | write_config($vmid, $conf); |
84e0c123 WB |
1667 | $lxc_setup->setup_network($conf); |
1668 | }; | |
bedeaaf1 | 1669 | |
f39002a6 DM |
1670 | &$change_ip_config(4); |
1671 | &$change_ip_config(6); | |
489e960d WL |
1672 | |
1673 | } | |
1674 | ||
a92f66c9 WL |
1675 | # Internal snapshots |
1676 | ||
1677 | # NOTE: Snapshot create/delete involves several non-atomic | |
c31ad455 FG |
1678 | # actions, and can take a long time. |
1679 | # So we try to avoid locking the file and use the 'lock' variable | |
a92f66c9 WL |
1680 | # inside the config file instead. |
1681 | ||
1682 | my $snapshot_copy_config = sub { | |
1683 | my ($source, $dest) = @_; | |
1684 | ||
1685 | foreach my $k (keys %$source) { | |
1686 | next if $k eq 'snapshots'; | |
09d3ec42 DM |
1687 | next if $k eq 'snapstate'; |
1688 | next if $k eq 'snaptime'; | |
1689 | next if $k eq 'vmstate'; | |
1690 | next if $k eq 'lock'; | |
a92f66c9 | 1691 | next if $k eq 'digest'; |
09d3ec42 | 1692 | next if $k eq 'description'; |
a92f66c9 WL |
1693 | |
1694 | $dest->{$k} = $source->{$k}; | |
1695 | } | |
1696 | }; | |
1697 | ||
1698 | my $snapshot_prepare = sub { | |
1699 | my ($vmid, $snapname, $comment) = @_; | |
1700 | ||
1701 | my $snap; | |
1702 | ||
1703 | my $updatefn = sub { | |
1704 | ||
1705 | my $conf = load_config($vmid); | |
1706 | ||
bb1ac2de DM |
1707 | die "you can't take a snapshot if it's a template\n" |
1708 | if is_template($conf); | |
1709 | ||
a92f66c9 WL |
1710 | check_lock($conf); |
1711 | ||
09d3ec42 | 1712 | $conf->{lock} = 'snapshot'; |
a92f66c9 WL |
1713 | |
1714 | die "snapshot name '$snapname' already used\n" | |
1715 | if defined($conf->{snapshots}->{$snapname}); | |
1716 | ||
1717 | my $storecfg = PVE::Storage::config(); | |
5d385379 FG |
1718 | my $feature = $snapname eq 'vzdump' ? 'vzdump' : 'snapshot'; |
1719 | die "snapshot feature is not available\n" if !has_feature($feature, $conf, $storecfg); | |
a92f66c9 WL |
1720 | |
1721 | $snap = $conf->{snapshots}->{$snapname} = {}; | |
1722 | ||
1723 | &$snapshot_copy_config($conf, $snap); | |
1724 | ||
09d3ec42 DM |
1725 | $snap->{'snapstate'} = "prepare"; |
1726 | $snap->{'snaptime'} = time(); | |
1727 | $snap->{'description'} = $comment if $comment; | |
a92f66c9 WL |
1728 | $conf->{snapshots}->{$snapname} = $snap; |
1729 | ||
706c9791 | 1730 | write_config($vmid, $conf); |
a92f66c9 WL |
1731 | }; |
1732 | ||
1733 | lock_container($vmid, 10, $updatefn); | |
1734 | ||
1735 | return $snap; | |
1736 | }; | |
1737 | ||
1738 | my $snapshot_commit = sub { | |
1739 | my ($vmid, $snapname) = @_; | |
1740 | ||
1741 | my $updatefn = sub { | |
1742 | ||
1743 | my $conf = load_config($vmid); | |
1744 | ||
1745 | die "missing snapshot lock\n" | |
09d3ec42 | 1746 | if !($conf->{lock} && $conf->{lock} eq 'snapshot'); |
a92f66c9 | 1747 | |
27916659 | 1748 | die "snapshot '$snapname' does not exist\n" |
a92f66c9 WL |
1749 | if !defined($conf->{snapshots}->{$snapname}); |
1750 | ||
1751 | die "wrong snapshot state\n" | |
09d3ec42 DM |
1752 | if !($conf->{snapshots}->{$snapname}->{'snapstate'} && |
1753 | $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare"); | |
a92f66c9 | 1754 | |
09d3ec42 DM |
1755 | delete $conf->{snapshots}->{$snapname}->{'snapstate'}; |
1756 | delete $conf->{lock}; | |
1757 | $conf->{parent} = $snapname; | |
a92f66c9 | 1758 | |
706c9791 | 1759 | write_config($vmid, $conf); |
a92f66c9 WL |
1760 | }; |
1761 | ||
1762 | lock_container($vmid, 10 ,$updatefn); | |
1763 | }; | |
1764 | ||
1765 | sub has_feature { | |
1766 | my ($feature, $conf, $storecfg, $snapname) = @_; | |
09d3ec42 | 1767 | |
a92f66c9 | 1768 | my $err; |
5d385379 FG |
1769 | my $vzdump = $feature eq 'vzdump'; |
1770 | $feature = 'snapshot' if $vzdump; | |
09d3ec42 | 1771 | |
8bf50651 DM |
1772 | foreach_mountpoint($conf, sub { |
1773 | my ($ms, $mountpoint) = @_; | |
1774 | ||
2c3ed8c4 | 1775 | return if $err; # skip further test |
5d385379 | 1776 | return if $vzdump && $ms ne 'rootfs' && !$mountpoint->{backup}; |
2c3ed8c4 | 1777 | |
8bf50651 DM |
1778 | $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname); |
1779 | ||
1780 | # TODO: implement support for mountpoints | |
1781 | die "unable to handle mountpoint '$ms' - feature not implemented\n" | |
1782 | if $ms ne 'rootfs'; | |
1783 | }); | |
a92f66c9 WL |
1784 | |
1785 | return $err ? 0 : 1; | |
1786 | } | |
1787 | ||
489e960d WL |
1788 | sub snapshot_create { |
1789 | my ($vmid, $snapname, $comment) = @_; | |
1790 | ||
a92f66c9 WL |
1791 | my $snap = &$snapshot_prepare($vmid, $snapname, $comment); |
1792 | ||
09d3ec42 | 1793 | my $conf = load_config($vmid); |
a92f66c9 | 1794 | |
a92f66c9 | 1795 | my $running = check_running($vmid); |
2477a7f1 DM |
1796 | |
1797 | my $unfreeze = 0; | |
74bf6d37 FG |
1798 | |
1799 | my $drivehash = {}; | |
1800 | ||
a92f66c9 WL |
1801 | eval { |
1802 | if ($running) { | |
2477a7f1 | 1803 | $unfreeze = 1; |
74bf6d37 | 1804 | PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]); |
4db769cf | 1805 | PVE::Tools::run_command(['/bin/sync']); |
a92f66c9 WL |
1806 | }; |
1807 | ||
1808 | my $storecfg = PVE::Storage::config(); | |
44a9face | 1809 | my $rootinfo = parse_ct_rootfs($conf->{rootfs}); |
09d3ec42 | 1810 | my $volid = $rootinfo->{volume}; |
a92f66c9 | 1811 | |
a92f66c9 | 1812 | PVE::Storage::volume_snapshot($storecfg, $volid, $snapname); |
74bf6d37 | 1813 | $drivehash->{rootfs} = 1; |
a92f66c9 | 1814 | }; |
2477a7f1 DM |
1815 | my $err = $@; |
1816 | ||
1817 | if ($unfreeze) { | |
1818 | eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); }; | |
1819 | warn $@ if $@; | |
1820 | } | |
1821 | ||
1822 | if ($err) { | |
74bf6d37 FG |
1823 | eval { snapshot_delete($vmid, $snapname, 1, $drivehash); }; |
1824 | warn "$@\n" if $@; | |
a92f66c9 WL |
1825 | die "$err\n"; |
1826 | } | |
74bf6d37 FG |
1827 | |
1828 | &$snapshot_commit($vmid, $snapname); | |
68a05bb3 AD |
1829 | } |
1830 | ||
74bf6d37 | 1831 | # Note: $drivehash is only set when called from snapshot_create. |
57ccb3f8 | 1832 | sub snapshot_delete { |
74bf6d37 | 1833 | my ($vmid, $snapname, $force, $drivehash) = @_; |
57ccb3f8 | 1834 | |
31429832 WL |
1835 | my $snap; |
1836 | ||
1837 | my $conf; | |
1838 | ||
1839 | my $updatefn = sub { | |
1840 | ||
1841 | $conf = load_config($vmid); | |
1842 | ||
bb1ac2de DM |
1843 | die "you can't delete a snapshot if vm is a template\n" |
1844 | if is_template($conf); | |
1845 | ||
31429832 WL |
1846 | $snap = $conf->{snapshots}->{$snapname}; |
1847 | ||
74bf6d37 FG |
1848 | if (!$drivehash) { |
1849 | check_lock($conf); | |
1850 | } | |
31429832 WL |
1851 | |
1852 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1853 | ||
09d3ec42 | 1854 | $snap->{snapstate} = 'delete'; |
31429832 | 1855 | |
706c9791 | 1856 | write_config($vmid, $conf); |
31429832 WL |
1857 | }; |
1858 | ||
1859 | lock_container($vmid, 10, $updatefn); | |
1860 | ||
1861 | my $storecfg = PVE::Storage::config(); | |
1862 | ||
7b2eb379 | 1863 | my $unlink_parent = sub { |
31429832 | 1864 | |
7b2eb379 | 1865 | my ($confref, $new_parent) = @_; |
31429832 | 1866 | |
7b2eb379 FG |
1867 | if ($confref->{parent} && $confref->{parent} eq $snapname) { |
1868 | if ($new_parent) { | |
1869 | $confref->{parent} = $new_parent; | |
31429832 | 1870 | } else { |
7b2eb379 | 1871 | delete $confref->{parent}; |
31429832 WL |
1872 | } |
1873 | } | |
7b2eb379 FG |
1874 | }; |
1875 | ||
1876 | my $del_snap = sub { | |
1877 | ||
74bf6d37 FG |
1878 | $conf = load_config($vmid); |
1879 | ||
1880 | if ($drivehash) { | |
1881 | delete $conf->{lock}; | |
1882 | } else { | |
1883 | check_lock($conf); | |
1884 | } | |
7b2eb379 FG |
1885 | |
1886 | my $parent = $conf->{snapshots}->{$snapname}->{parent}; | |
1887 | foreach my $snapkey (keys %{$conf->{snapshots}}) { | |
1888 | &$unlink_parent($conf->{snapshots}->{$snapkey}, $parent); | |
1889 | } | |
1890 | ||
1891 | &$unlink_parent($conf, $parent); | |
31429832 WL |
1892 | |
1893 | delete $conf->{snapshots}->{$snapname}; | |
1894 | ||
706c9791 | 1895 | write_config($vmid, $conf); |
31429832 WL |
1896 | }; |
1897 | ||
09d3ec42 | 1898 | my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs}; |
44a9face | 1899 | my $rootinfo = parse_ct_rootfs($rootfs); |
09d3ec42 | 1900 | my $volid = $rootinfo->{volume}; |
31429832 WL |
1901 | |
1902 | eval { | |
1903 | PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname); | |
1904 | }; | |
1905 | my $err = $@; | |
1906 | ||
1907 | if(!$err || ($err && $force)) { | |
1908 | lock_container($vmid, 10, $del_snap); | |
1909 | if ($err) { | |
1910 | die "Can't delete snapshot: $vmid $snapname $err\n"; | |
1911 | } | |
1912 | } | |
57ccb3f8 WL |
1913 | } |
1914 | ||
723157f6 WL |
1915 | sub snapshot_rollback { |
1916 | my ($vmid, $snapname) = @_; | |
1917 | ||
6860ba0c WL |
1918 | my $storecfg = PVE::Storage::config(); |
1919 | ||
1920 | my $conf = load_config($vmid); | |
1921 | ||
bb1ac2de DM |
1922 | die "you can't rollback if vm is a template\n" if is_template($conf); |
1923 | ||
6860ba0c WL |
1924 | my $snap = $conf->{snapshots}->{$snapname}; |
1925 | ||
1926 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1927 | ||
09d3ec42 | 1928 | my $rootfs = $snap->{rootfs}; |
44a9face | 1929 | my $rootinfo = parse_ct_rootfs($rootfs); |
09d3ec42 DM |
1930 | my $volid = $rootinfo->{volume}; |
1931 | ||
1932 | PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname); | |
6860ba0c WL |
1933 | |
1934 | my $updatefn = sub { | |
1935 | ||
09d3ec42 DM |
1936 | die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" |
1937 | if $snap->{snapstate}; | |
6860ba0c WL |
1938 | |
1939 | check_lock($conf); | |
6860ba0c | 1940 | |
b935932a | 1941 | system("lxc-stop -n $vmid --kill") if check_running($vmid); |
6860ba0c WL |
1942 | |
1943 | die "unable to rollback vm $vmid: vm is running\n" | |
1944 | if check_running($vmid); | |
1945 | ||
09d3ec42 | 1946 | $conf->{lock} = 'rollback'; |
6860ba0c WL |
1947 | |
1948 | my $forcemachine; | |
1949 | ||
1950 | # copy snapshot config to current config | |
1951 | ||
1952 | my $tmp_conf = $conf; | |
1953 | &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf); | |
6860ba0c | 1954 | $conf->{snapshots} = $tmp_conf->{snapshots}; |
09d3ec42 DM |
1955 | delete $conf->{snaptime}; |
1956 | delete $conf->{snapname}; | |
1957 | $conf->{parent} = $snapname; | |
6860ba0c | 1958 | |
706c9791 | 1959 | write_config($vmid, $conf); |
6860ba0c WL |
1960 | }; |
1961 | ||
1962 | my $unlockfn = sub { | |
09d3ec42 | 1963 | delete $conf->{lock}; |
706c9791 | 1964 | write_config($vmid, $conf); |
6860ba0c WL |
1965 | }; |
1966 | ||
1967 | lock_container($vmid, 10, $updatefn); | |
1968 | ||
09d3ec42 | 1969 | PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname); |
6860ba0c WL |
1970 | |
1971 | lock_container($vmid, 5, $unlockfn); | |
723157f6 | 1972 | } |
b935932a | 1973 | |
bb1ac2de DM |
1974 | sub template_create { |
1975 | my ($vmid, $conf) = @_; | |
1976 | ||
1977 | my $storecfg = PVE::Storage::config(); | |
1978 | ||
44a9face | 1979 | my $rootinfo = parse_ct_rootfs($conf->{rootfs}); |
bb1ac2de DM |
1980 | my $volid = $rootinfo->{volume}; |
1981 | ||
1982 | die "Template feature is not available for '$volid'\n" | |
1983 | if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid); | |
1984 | ||
1985 | PVE::Storage::activate_volumes($storecfg, [$volid]); | |
1986 | ||
1987 | my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid); | |
1988 | $rootinfo->{volume} = $template_volid; | |
4fee75fd | 1989 | $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1); |
bb1ac2de DM |
1990 | |
1991 | write_config($vmid, $conf); | |
1992 | } | |
1993 | ||
1994 | sub is_template { | |
1995 | my ($conf) = @_; | |
1996 | ||
1997 | return 1 if defined $conf->{template} && $conf->{template} == 1; | |
1998 | } | |
1999 | ||
9622e848 DM |
2000 | sub mountpoint_names { |
2001 | my ($reverse) = @_; | |
ced7fddb | 2002 | |
9622e848 | 2003 | my @names = ('rootfs'); |
eaebef36 DM |
2004 | |
2005 | for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) { | |
9622e848 DM |
2006 | push @names, "mp$i"; |
2007 | } | |
2008 | ||
2009 | return $reverse ? reverse @names : @names; | |
2010 | } | |
2011 | ||
3c9dbfa9 | 2012 | |
9622e848 DM |
2013 | sub foreach_mountpoint_full { |
2014 | my ($conf, $reverse, $func) = @_; | |
2015 | ||
2016 | foreach my $key (mountpoint_names($reverse)) { | |
2017 | my $value = $conf->{$key}; | |
2018 | next if !defined($value); | |
44a9face | 2019 | my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1); |
ca7feb1a | 2020 | next if !defined($mountpoint); |
3c9dbfa9 | 2021 | |
eaebef36 | 2022 | &$func($key, $mountpoint); |
ced7fddb AD |
2023 | } |
2024 | } | |
2025 | ||
9622e848 DM |
2026 | sub foreach_mountpoint { |
2027 | my ($conf, $func) = @_; | |
2028 | ||
2029 | foreach_mountpoint_full($conf, 0, $func); | |
2030 | } | |
2031 | ||
2032 | sub foreach_mountpoint_reverse { | |
2033 | my ($conf, $func) = @_; | |
2034 | ||
2035 | foreach_mountpoint_full($conf, 1, $func); | |
2036 | } | |
2037 | ||
52389a07 DM |
2038 | sub check_ct_modify_config_perm { |
2039 | my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_; | |
2040 | ||
2041 | return 1 if $authuser ne 'root@pam'; | |
2042 | ||
2043 | foreach my $opt (@$key_list) { | |
2044 | ||
2045 | if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') { | |
2046 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']); | |
e59a61ed | 2047 | } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) { |
52389a07 DM |
2048 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); |
2049 | } elsif ($opt eq 'memory' || $opt eq 'swap') { | |
2050 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']); | |
2051 | } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' || | |
2052 | $opt eq 'searchdomain' || $opt eq 'hostname') { | |
2053 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']); | |
2054 | } else { | |
2055 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); | |
2056 | } | |
2057 | } | |
2058 | ||
2059 | return 1; | |
2060 | } | |
2061 | ||
9622e848 | 2062 | sub umount_all { |
da629848 | 2063 | my ($vmid, $storage_cfg, $conf, $noerr) = @_; |
9622e848 DM |
2064 | |
2065 | my $rootdir = "/var/lib/lxc/$vmid/rootfs"; | |
2066 | my $volid_list = get_vm_volumes($conf); | |
2067 | ||
2068 | foreach_mountpoint_reverse($conf, sub { | |
2069 | my ($ms, $mountpoint) = @_; | |
2070 | ||
2071 | my $volid = $mountpoint->{volume}; | |
2072 | my $mount = $mountpoint->{mp}; | |
2073 | ||
2074 | return if !$volid || !$mount; | |
2075 | ||
d18f96b4 | 2076 | my $mount_path = "$rootdir/$mount"; |
f845a93d | 2077 | $mount_path =~ s!/+!/!g; |
9622e848 | 2078 | |
228a5a1d WL |
2079 | return if !PVE::ProcFSTools::is_mounted($mount_path); |
2080 | ||
9622e848 | 2081 | eval { |
d18f96b4 | 2082 | PVE::Tools::run_command(['umount', '-d', $mount_path]); |
9622e848 DM |
2083 | }; |
2084 | if (my $err = $@) { | |
2085 | if ($noerr) { | |
2086 | warn $err; | |
2087 | } else { | |
2088 | die $err; | |
2089 | } | |
2090 | } | |
2091 | }); | |
9622e848 DM |
2092 | } |
2093 | ||
2094 | sub mount_all { | |
7b49dfe0 | 2095 | my ($vmid, $storage_cfg, $conf) = @_; |
9622e848 DM |
2096 | |
2097 | my $rootdir = "/var/lib/lxc/$vmid/rootfs"; | |
1adc7e53 | 2098 | File::Path::make_path($rootdir); |
9622e848 DM |
2099 | |
2100 | my $volid_list = get_vm_volumes($conf); | |
2101 | PVE::Storage::activate_volumes($storage_cfg, $volid_list); | |
2102 | ||
2103 | eval { | |
9622e848 DM |
2104 | foreach_mountpoint($conf, sub { |
2105 | my ($ms, $mountpoint) = @_; | |
2106 | ||
da629848 | 2107 | mountpoint_mount($mountpoint, $rootdir, $storage_cfg); |
9622e848 DM |
2108 | }); |
2109 | }; | |
2110 | if (my $err = $@) { | |
e2007ac2 | 2111 | warn "mounting container failed\n"; |
9622e848 | 2112 | umount_all($vmid, $storage_cfg, $conf, 1); |
e2007ac2 | 2113 | die $err; |
9622e848 DM |
2114 | } |
2115 | ||
da629848 | 2116 | return $rootdir; |
9622e848 DM |
2117 | } |
2118 | ||
2119 | ||
b15c75fc | 2120 | sub mountpoint_mount_path { |
da629848 | 2121 | my ($mountpoint, $storage_cfg, $snapname) = @_; |
b15c75fc | 2122 | |
da629848 | 2123 | return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname); |
b15c75fc | 2124 | } |
cc6b0307 | 2125 | |
2cfae16e WB |
2126 | my $check_mount_path = sub { |
2127 | my ($path) = @_; | |
2128 | $path = File::Spec->canonpath($path); | |
2129 | my $real = Cwd::realpath($path); | |
2130 | if ($real ne $path) { | |
2131 | die "mount path modified by symlink: $path != $real"; | |
2132 | } | |
2133 | }; | |
2134 | ||
21f292ff WB |
2135 | sub query_loopdev { |
2136 | my ($path) = @_; | |
2137 | my $found; | |
2138 | my $parser = sub { | |
2139 | my $line = shift; | |
2140 | if ($line =~ m@^(/dev/loop\d+):@) { | |
2141 | $found = $1; | |
2142 | } | |
2143 | }; | |
2144 | my $cmd = ['losetup', '--associated', $path]; | |
2145 | PVE::Tools::run_command($cmd, outfunc => $parser); | |
2146 | return $found; | |
2147 | } | |
2148 | ||
b15c75fc | 2149 | # use $rootdir = undef to just return the corresponding mount path |
cc6b0307 | 2150 | sub mountpoint_mount { |
da629848 | 2151 | my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_; |
cc6b0307 AD |
2152 | |
2153 | my $volid = $mountpoint->{volume}; | |
2154 | my $mount = $mountpoint->{mp}; | |
7c921c80 | 2155 | my $type = $mountpoint->{type}; |
b15c75fc | 2156 | |
cc6b0307 AD |
2157 | return if !$volid || !$mount; |
2158 | ||
b15c75fc DM |
2159 | my $mount_path; |
2160 | ||
2161 | if (defined($rootdir)) { | |
2162 | $rootdir =~ s!/+$!!; | |
2163 | $mount_path = "$rootdir/$mount"; | |
f845a93d | 2164 | $mount_path =~ s!/+!/!g; |
2cfae16e | 2165 | &$check_mount_path($mount_path); |
b15c75fc | 2166 | File::Path::mkpath($mount_path); |
116ce06f | 2167 | } |
b15c75fc DM |
2168 | |
2169 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
cc6b0307 | 2170 | |
b15c75fc | 2171 | die "unknown snapshot path for '$volid'" if !$storage && defined($snapname); |
cc6b0307 | 2172 | |
471dd315 WB |
2173 | my $optstring = ''; |
2174 | if (defined($mountpoint->{acl})) { | |
2175 | $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl'); | |
2176 | } | |
2177 | if ($mountpoint->{ro}) { | |
2178 | $optstring .= ',' if $optstring; | |
2179 | $optstring .= 'ro'; | |
2180 | } | |
2181 | ||
2182 | my @extra_opts = ('-o', $optstring); | |
2183 | ||
b15c75fc DM |
2184 | if ($storage) { |
2185 | ||
2186 | my $scfg = PVE::Storage::storage_config($storage_cfg, $storage); | |
2187 | my $path = PVE::Storage::path($storage_cfg, $volid, $snapname); | |
2188 | ||
2189 | my ($vtype, undef, undef, undef, undef, $isBase, $format) = | |
2190 | PVE::Storage::parse_volname($storage_cfg, $volid); | |
2191 | ||
c87b9dd8 DM |
2192 | $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files |
2193 | ||
b15c75fc | 2194 | if ($format eq 'subvol') { |
30de33be DM |
2195 | if ($mount_path) { |
2196 | if ($snapname) { | |
e84f7f5d DM |
2197 | if ($scfg->{type} eq 'zfspool') { |
2198 | my $path_arg = $path; | |
2199 | $path_arg =~ s!^/+!!; | |
471dd315 | 2200 | PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]); |
e84f7f5d | 2201 | } else { |
30de33be DM |
2202 | die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n"; |
2203 | } | |
e84f7f5d | 2204 | } else { |
471dd315 WB |
2205 | if ($mountpoint->{ro}) { |
2206 | die "read-only bind mounts not supported\n"; | |
2207 | } | |
2208 | PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]); | |
30de33be | 2209 | } |
b15c75fc | 2210 | } |
30de33be | 2211 | return wantarray ? ($path, 0) : $path; |
c87b9dd8 | 2212 | } elsif ($format eq 'raw' || $format eq 'iso') { |
30de33be | 2213 | my $use_loopdev = 0; |
b15c75fc | 2214 | if ($scfg->{path}) { |
da629848 | 2215 | push @extra_opts, '-o', 'loop'; |
30de33be | 2216 | $use_loopdev = 1; |
2e879877 DM |
2217 | } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || |
2218 | $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') { | |
b15c75fc DM |
2219 | # do nothing |
2220 | } else { | |
2221 | die "unsupported storage type '$scfg->{type}'\n"; | |
2222 | } | |
30de33be | 2223 | if ($mount_path) { |
c87b9dd8 DM |
2224 | if ($format eq 'iso') { |
2225 | PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]); | |
2226 | } elsif ($isBase || defined($snapname)) { | |
9d7d4d30 | 2227 | PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]); |
30de33be DM |
2228 | } else { |
2229 | PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]); | |
2230 | } | |
b15c75fc | 2231 | } |
30de33be | 2232 | return wantarray ? ($path, $use_loopdev) : $path; |
b15c75fc DM |
2233 | } else { |
2234 | die "unsupported image format '$format'\n"; | |
2235 | } | |
7c921c80 | 2236 | } elsif ($type eq 'device') { |
471dd315 | 2237 | PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path; |
30de33be | 2238 | return wantarray ? ($volid, 0) : $volid; |
e2007ac2 | 2239 | } elsif ($type eq 'bind') { |
471dd315 WB |
2240 | if ($mountpoint->{ro}) { |
2241 | die "read-only bind mounts not supported\n"; | |
2242 | # Theoretically we'd have to execute both: | |
2243 | # mount -o bind $a $b | |
2244 | # mount -o bind,remount,ro $a $b | |
2245 | } | |
e2007ac2 | 2246 | die "directory '$volid' does not exist\n" if ! -d $volid; |
2cfae16e | 2247 | &$check_mount_path($volid); |
471dd315 | 2248 | PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path; |
30de33be | 2249 | return wantarray ? ($volid, 0) : $volid; |
b15c75fc DM |
2250 | } |
2251 | ||
2252 | die "unsupported storage"; | |
cc6b0307 AD |
2253 | } |
2254 | ||
9205e9d0 AD |
2255 | sub get_vm_volumes { |
2256 | my ($conf, $excludes) = @_; | |
2257 | ||
2258 | my $vollist = []; | |
2259 | ||
706c9791 | 2260 | foreach_mountpoint($conf, sub { |
9205e9d0 AD |
2261 | my ($ms, $mountpoint) = @_; |
2262 | ||
2263 | return if $excludes && $ms eq $excludes; | |
2264 | ||
2265 | my $volid = $mountpoint->{volume}; | |
2266 | ||
7c921c80 | 2267 | return if !$volid || $mountpoint->{type} ne 'volume'; |
9205e9d0 AD |
2268 | |
2269 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
2270 | return if !$sid; | |
2271 | ||
2272 | push @$vollist, $volid; | |
2273 | }); | |
2274 | ||
2275 | return $vollist; | |
2276 | } | |
2277 | ||
6c871c36 | 2278 | sub mkfs { |
d216e891 | 2279 | my ($dev, $rootuid, $rootgid) = @_; |
6c871c36 | 2280 | |
d216e891 WB |
2281 | PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', |
2282 | '-E', "root_owner=$rootuid:$rootgid", | |
2283 | $dev]); | |
6c871c36 DM |
2284 | } |
2285 | ||
2286 | sub format_disk { | |
d216e891 | 2287 | my ($storage_cfg, $volid, $rootuid, $rootgid) = @_; |
6c871c36 DM |
2288 | |
2289 | if ($volid =~ m!^/dev/.+!) { | |
2290 | mkfs($volid); | |
2291 | return; | |
2292 | } | |
2293 | ||
2294 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
2295 | ||
2296 | die "cannot format volume '$volid' with no storage\n" if !$storage; | |
2297 | ||
08ca136d DM |
2298 | PVE::Storage::activate_volumes($storage_cfg, [$volid]); |
2299 | ||
6c871c36 DM |
2300 | my $path = PVE::Storage::path($storage_cfg, $volid); |
2301 | ||
2302 | my ($vtype, undef, undef, undef, undef, $isBase, $format) = | |
2303 | PVE::Storage::parse_volname($storage_cfg, $volid); | |
2304 | ||
2305 | die "cannot format volume '$volid' (format == $format)\n" | |
2306 | if $format ne 'raw'; | |
2307 | ||
d216e891 | 2308 | mkfs($path, $rootuid, $rootgid); |
6c871c36 DM |
2309 | } |
2310 | ||
2311 | sub destroy_disks { | |
2312 | my ($storecfg, $vollist) = @_; | |
2313 | ||
2314 | foreach my $volid (@$vollist) { | |
2315 | eval { PVE::Storage::vdisk_free($storecfg, $volid); }; | |
2316 | warn $@ if $@; | |
2317 | } | |
2318 | } | |
2319 | ||
2320 | sub create_disks { | |
2321 | my ($storecfg, $vmid, $settings, $conf) = @_; | |
2322 | ||
2323 | my $vollist = []; | |
2324 | ||
2325 | eval { | |
d216e891 WB |
2326 | my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); |
2327 | my $chown_vollist = []; | |
2328 | ||
6c871c36 DM |
2329 | foreach_mountpoint($settings, sub { |
2330 | my ($ms, $mountpoint) = @_; | |
2331 | ||
2332 | my $volid = $mountpoint->{volume}; | |
2333 | my $mp = $mountpoint->{mp}; | |
2334 | ||
2335 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1); | |
2336 | ||
e2007ac2 | 2337 | if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) { |
8ed5ff9d | 2338 | my ($storeid, $size_gb) = ($1, $2); |
6c871c36 | 2339 | |
8ed5ff9d | 2340 | my $size_kb = int(${size_gb}*1024) * 1024; |
6c871c36 DM |
2341 | |
2342 | my $scfg = PVE::Storage::storage_config($storecfg, $storage); | |
2343 | # fixme: use better naming ct-$vmid-disk-X.raw? | |
2344 | ||
2345 | if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') { | |
8ed5ff9d | 2346 | if ($size_kb > 0) { |
6c871c36 | 2347 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', |
8ed5ff9d | 2348 | undef, $size_kb); |
d216e891 | 2349 | format_disk($storecfg, $volid, $rootuid, $rootgid); |
6c871c36 DM |
2350 | } else { |
2351 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol', | |
2352 | undef, 0); | |
d216e891 | 2353 | push @$chown_vollist, $volid; |
6c871c36 DM |
2354 | } |
2355 | } elsif ($scfg->{type} eq 'zfspool') { | |
2356 | ||
2357 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol', | |
8ed5ff9d | 2358 | undef, $size_kb); |
d216e891 | 2359 | push @$chown_vollist, $volid; |
2e879877 | 2360 | } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') { |
6c871c36 | 2361 | |
8ed5ff9d | 2362 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb); |
d216e891 | 2363 | format_disk($storecfg, $volid, $rootuid, $rootgid); |
6c871c36 DM |
2364 | |
2365 | } elsif ($scfg->{type} eq 'rbd') { | |
2366 | ||
2367 | die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd}; | |
8ed5ff9d | 2368 | $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb); |
d216e891 | 2369 | format_disk($storecfg, $volid, $rootuid, $rootgid); |
6c871c36 DM |
2370 | } else { |
2371 | die "unable to create containers on storage type '$scfg->{type}'\n"; | |
2372 | } | |
2373 | push @$vollist, $volid; | |
71c780b9 WB |
2374 | $mountpoint->{volume} = $volid; |
2375 | $mountpoint->{size} = $size_kb * 1024; | |
2376 | $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs'); | |
6c871c36 | 2377 | } else { |
e2007ac2 DM |
2378 | # use specified/existing volid/dir/device |
2379 | $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs'); | |
6c871c36 DM |
2380 | } |
2381 | }); | |
d216e891 WB |
2382 | |
2383 | PVE::Storage::activate_volumes($storecfg, $chown_vollist, undef); | |
2384 | foreach my $volid (@$chown_vollist) { | |
2385 | my $path = PVE::Storage::path($storecfg, $volid, undef); | |
2386 | chown($rootuid, $rootgid, $path); | |
2387 | } | |
2388 | PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef); | |
6c871c36 DM |
2389 | }; |
2390 | # free allocated images on error | |
2391 | if (my $err = $@) { | |
2392 | destroy_disks($storecfg, $vollist); | |
2393 | die $err; | |
2394 | } | |
2395 | return $vollist; | |
2396 | } | |
2397 | ||
68e8f3c5 DM |
2398 | # bash completion helper |
2399 | ||
2400 | sub complete_os_templates { | |
2401 | my ($cmdname, $pname, $cvalue) = @_; | |
2402 | ||
2403 | my $cfg = PVE::Storage::config(); | |
2404 | ||
9e9bc3a6 | 2405 | my $storeid; |
68e8f3c5 DM |
2406 | |
2407 | if ($cvalue =~ m/^([^:]+):/) { | |
2408 | $storeid = $1; | |
2409 | } | |
2410 | ||
2411 | my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl'; | |
2412 | my $data = PVE::Storage::template_list($cfg, $storeid, $vtype); | |
2413 | ||
2414 | my $res = []; | |
2415 | foreach my $id (keys %$data) { | |
2416 | foreach my $item (@{$data->{$id}}) { | |
2417 | push @$res, $item->{volid} if defined($item->{volid}); | |
2418 | } | |
2419 | } | |
2420 | ||
2421 | return $res; | |
2422 | } | |
2423 | ||
68e8f3c5 DM |
2424 | my $complete_ctid_full = sub { |
2425 | my ($running) = @_; | |
2426 | ||
2427 | my $idlist = vmstatus(); | |
2428 | ||
2429 | my $active_hash = list_active_containers(); | |
2430 | ||
2431 | my $res = []; | |
2432 | ||
2433 | foreach my $id (keys %$idlist) { | |
2434 | my $d = $idlist->{$id}; | |
2435 | if (defined($running)) { | |
2436 | next if $d->{template}; | |
2437 | next if $running && !$active_hash->{$id}; | |
2438 | next if !$running && $active_hash->{$id}; | |
2439 | } | |
2440 | push @$res, $id; | |
2441 | ||
2442 | } | |
2443 | return $res; | |
2444 | }; | |
2445 | ||
2446 | sub complete_ctid { | |
2447 | return &$complete_ctid_full(); | |
2448 | } | |
2449 | ||
2450 | sub complete_ctid_stopped { | |
2451 | return &$complete_ctid_full(0); | |
2452 | } | |
2453 | ||
2454 | sub complete_ctid_running { | |
2455 | return &$complete_ctid_full(1); | |
2456 | } | |
2457 | ||
c6a605f9 WB |
2458 | sub parse_id_maps { |
2459 | my ($conf) = @_; | |
2460 | ||
2461 | my $id_map = []; | |
2462 | my $rootuid = 0; | |
2463 | my $rootgid = 0; | |
2464 | ||
2465 | my $lxc = $conf->{lxc}; | |
2466 | foreach my $entry (@$lxc) { | |
2467 | my ($key, $value) = @$entry; | |
2468 | next if $key ne 'lxc.id_map'; | |
2469 | if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) { | |
2470 | my ($type, $ct, $host, $length) = ($1, $2, $3, $4); | |
2471 | push @$id_map, [$type, $ct, $host, $length]; | |
2472 | if ($ct == 0) { | |
2473 | $rootuid = $host if $type eq 'u'; | |
2474 | $rootgid = $host if $type eq 'g'; | |
2475 | } | |
2476 | } else { | |
2477 | die "failed to parse id_map: $value\n"; | |
2478 | } | |
2479 | } | |
2480 | ||
2481 | if (!@$id_map && $conf->{unprivileged}) { | |
2482 | # Should we read them from /etc/subuid? | |
2483 | $id_map = [ ['u', '0', '100000', '65536'], | |
2484 | ['g', '0', '100000', '65536'] ]; | |
2485 | $rootuid = $rootgid = 100000; | |
2486 | } | |
2487 | ||
2488 | return ($id_map, $rootuid, $rootgid); | |
2489 | } | |
2490 | ||
01dce99b WB |
2491 | sub userns_command { |
2492 | my ($id_map) = @_; | |
2493 | if (@$id_map) { | |
2494 | return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--']; | |
2495 | } | |
2496 | return []; | |
2497 | } | |
2498 | ||
f76a2828 | 2499 | 1; |