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