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