]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
Revert "lxc-start does not close all FDs, so we cannot use run_command"
[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;
8use Fcntl ':flock';
9
10use PVE::Cluster qw(cfs_register_file cfs_read_file);
c65e0a6d 11use PVE::Storage;
f76a2828
DM
12use PVE::SafeSyslog;
13use PVE::INotify;
a3249355 14use PVE::JSONSchema qw(get_standard_option);
55fa4e09 15use PVE::Tools qw($IPV6RE $IPV4RE);
f76a2828
DM
16
17use Data::Dumper;
18
19cfs_register_file('/lxc/', \&parse_lxc_config, \&write_lxc_config);
20
7dfc49cc
DM
21PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
22sub verify_lxc_network {
23 my ($value, $noerr) = @_;
24
25 return $value if parse_lxc_network($value);
26
27 return undef if $noerr;
28
29 die "unable to parse network setting\n";
30}
31
f76a2828
DM
32my $nodename = PVE::INotify::nodename();
33
822de0c3
DM
34sub parse_lxc_size {
35 my ($name, $value) = @_;
36
37 if ($value =~ m/^(\d+)(b|k|m|g)?$/i) {
38 my ($res, $unit) = ($1, lc($2 || 'b'));
39
40 return $res if $unit eq 'b';
41 return $res*1024 if $unit eq 'k';
42 return $res*1024*1024 if $unit eq 'm';
43 return $res*1024*1024*1024 if $unit eq 'g';
44 }
45
46 return undef;
47}
48
f76a2828 49my $valid_lxc_keys = {
822de0c3 50 'lxc.arch' => 'i386|x86|i686|x86_64|amd64',
f76a2828
DM
51 'lxc.include' => 1,
52 'lxc.rootfs' => 1,
53 'lxc.mount' => 1,
54 'lxc.utsname' => 1,
55
54664cd3 56 'lxc.id_map' => 1,
822de0c3
DM
57
58 'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
44da0641 59 'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
b80dd50a
DM
60 'lxc.cgroup.cpu.cfs_period_us' => '\d+',
61 'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
62 'lxc.cgroup.cpu.shares' => '\d+',
822de0c3 63
54664cd3
DM
64 # mount related
65 'lxc.mount' => 1,
66 'lxc.mount.entry' => 1,
67 'lxc.mount.auto' => 1,
68
69 # not used by pve
b80dd50a 70 'lxc.tty' => '\d+',
54664cd3
DM
71 'lxc.pts' => 1,
72 'lxc.haltsignal' => 1,
73 'lxc.rebootsignal' => 1,
74 'lxc.stopsignal' => 1,
75 'lxc.init_cmd' => 1,
76 'lxc.console' => 1,
77 'lxc.console.logfile' => 1,
78 'lxc.devttydir' => 1,
79 'lxc.autodev' => 1,
80 'lxc.kmsg' => 1,
81 'lxc.cap.drop' => 1,
82 'lxc.cap.keep' => 1,
83 'lxc.aa_profile' => 1,
84 'lxc.aa_allow_incomplete' => 1,
85 'lxc.se_context' => 1,
86 'lxc.loglevel' => 1,
87 'lxc.logfile' => 1,
88 'lxc.environment' => 1,
89
90
91 # autostart
92 'lxc.start.auto' => 1,
93 'lxc.start.delay' => 1,
94 'lxc.start.order' => 1,
95 'lxc.group' => 1,
96
97 # hooks
98 'lxc.hook.pre-start' => 1,
99 'lxc.hook.pre-mount' => 1,
100 'lxc.hook.mount' => 1,
101 'lxc.hook.autodev' => 1,
102 'lxc.hook.start' => 1,
103 'lxc.hook.post-stop' => 1,
104 'lxc.hook.clone' => 1,
105
f76a2828 106 # pve related keys
117636e5
DM
107 'pve.nameserver' => sub {
108 my ($name, $value) = @_;
109 return verify_nameserver_list($value);
110 },
111 'pve.searchdomain' => sub {
112 my ($name, $value) = @_;
113 return verify_searchdomain_list($value);
114 },
a99b3509 115 'pve.onboot' => '(0|1)',
a3249355
DM
116 'pve.startup' => sub {
117 my ($name, $value) = @_;
118 return PVE::JSONSchema::pve_verify_startup_order($value);
119 },
f76a2828 120 'pve.comment' => 1,
10fc3ba5 121 'pve.disksize' => '\d+(\.\d+)?',
611fe3aa
DM
122 'pve.volid' => sub {
123 my ($name, $value) = @_;
124 PVE::Storage::parse_volume_id($value);
125 return $value;
126 },
f76a2828
DM
127};
128
93285df8 129my $valid_lxc_network_keys = {
f76a2828 130 type => 1,
f76a2828
DM
131 mtu => 1,
132 name => 1, # ifname inside container
133 'veth.pair' => 1, # ifname at host (eth${vmid}.X)
134 hwaddr => 1,
93285df8
DM
135};
136
137my $valid_pve_network_keys = {
8a04b6c7 138 bridge => 1,
2b1fc2ea
DM
139 tag => 1,
140 firewall => 1,
93285df8
DM
141 ip => 1,
142 gw => 1,
143 ip6 => 1,
144 gw6 => 1,
f76a2828
DM
145};
146
147my $lxc_array_configs = {
148 'lxc.network' => 1,
149 'lxc.mount' => 1,
150 'lxc.include' => 1,
5b4657d0 151 'lxc.id_map' => 1,
f76a2828
DM
152};
153
154sub write_lxc_config {
155 my ($filename, $data) = @_;
156
157 my $raw = "";
158
159 return $raw if !$data;
160
161 my $done_hash = { digest => 1};
7dfc49cc 162
63e9c5ca
DM
163 my $dump_entry = sub {
164 my ($k) = @_;
165 my $value = $data->{$k};
166 return if !defined($value);
167 return if $done_hash->{$k};
f76a2828 168 $done_hash->{$k} = 1;
63e9c5ca
DM
169 if (ref($value)) {
170 die "got unexpected reference for '$k'"
171 if !$lxc_array_configs->{$k};
172 foreach my $v (@$value) {
5b4657d0
DM
173 $raw .= "$k = $v\n";
174 }
175 } else {
63e9c5ca 176 $raw .= "$k = $value\n";
5b4657d0 177 }
63e9c5ca
DM
178 };
179
180 # Note: Order is important! Include defaults first, so that we
181 # can overwrite them later.
182 &$dump_entry('lxc.include');
183
184 foreach my $k (sort keys %$data) {
185 next if $k !~ m/^lxc\./;
186 &$dump_entry($k);
f76a2828 187 }
7dfc49cc 188
f76a2828 189 foreach my $k (sort keys %$data) {
fff3a342 190 next if $k !~ m/^pve\./;
63e9c5ca 191 &$dump_entry($k);
fff3a342
DM
192 }
193
160f0941 194 my $network_count = 0;
fff3a342 195 foreach my $k (sort keys %$data) {
f76a2828
DM
196 next if $k !~ m/^net\d+$/;
197 $done_hash->{$k} = 1;
198 my $net = $data->{$k};
160f0941 199 $network_count++;
f76a2828
DM
200 $raw .= "lxc.network.type = $net->{type}\n";
201 foreach my $subkey (sort keys %$net) {
202 next if $subkey eq 'type';
93285df8
DM
203 if ($valid_lxc_network_keys->{$subkey}) {
204 $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
205 } elsif ($valid_pve_network_keys->{$subkey}) {
206 $raw .= "pve.network.$subkey = $net->{$subkey}\n";
207 } else {
208 die "found invalid network key '$subkey'";
209 }
f76a2828
DM
210 }
211 }
212
160f0941
DM
213 if (!$network_count) {
214 $raw .= "lxc.network.type = empty\n";
215 }
216
f76a2828
DM
217 foreach my $k (sort keys %$data) {
218 next if $done_hash->{$k};
219 die "found un-written value in config - implement this!";
220 }
221
f76a2828
DM
222 return $raw;
223}
224
822de0c3
DM
225sub parse_lxc_option {
226 my ($name, $value) = @_;
227
228 my $parser = $valid_lxc_keys->{$name};
229
230 die "inavlid key '$name'\n" if !defined($parser);
231
232 if ($parser eq '1') {
233 return $value;
234 } elsif (ref($parser)) {
235 my $res = &$parser($name, $value);
236 return $res if defined($res);
237 } else {
238 # assume regex
239 return $value if $value =~ m/^$parser$/;
240 }
241
242 die "unable to parse value '$value' for option '$name'\n";
243}
244
f76a2828
DM
245sub parse_lxc_config {
246 my ($filename, $raw) = @_;
247
248 return undef if !defined($raw);
249
250 my $data = {
251 digest => Digest::SHA::sha1_hex($raw),
252 };
253
254 $filename =~ m|/lxc/(\d+)/config$|
255 || die "got strange filename '$filename'";
256
257 my $vmid = $1;
258
259 my $network_counter = 0;
260 my $network_list = [];
261 my $host_ifnames = {};
262
263 my $find_next_hostif_name = sub {
264 for (my $i = 0; $i < 10; $i++) {
265 my $name = "veth${vmid}.$i";
266 if (!$host_ifnames->{$name}) {
267 $host_ifnames->{$name} = 1;
268 return $name;
269 }
270 }
271
272 die "unable to find free host_ifname"; # should not happen
273 };
7dfc49cc 274
f76a2828
DM
275 my $push_network = sub {
276 my ($netconf) = @_;
277 return if !$netconf;
278 push @{$network_list}, $netconf;
279 $network_counter++;
280 if (my $netname = $netconf->{'veth.pair'}) {
281 if ($netname =~ m/^veth(\d+).(\d)$/) {
282 die "wrong vmid for network interface pair\n" if $1 != $vmid;
283 my $host_ifnames->{$netname} = 1;
284 } else {
285 die "wrong network interface pair\n";
286 }
287 }
288 };
289
290 my $network;
7dfc49cc 291
f76a2828
DM
292 while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
293 my $line = $1;
294
295 next if $line =~ m/^\#/;
296 next if $line =~ m/^\s*$/;
297
298 if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
299 my ($subkey, $value) = ($1, $2);
300 if ($subkey eq 'type') {
301 &$push_network($network);
302 $network = { type => $value };
93285df8
DM
303 } elsif ($valid_lxc_network_keys->{$subkey}) {
304 $network->{$subkey} = $value;
305 } else {
306 die "unable to parse config line: $line\n";
307 }
308 next;
309 }
310 if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
311 my ($subkey, $value) = ($1, $2);
312 if ($valid_pve_network_keys->{$subkey}) {
f76a2828
DM
313 $network->{$subkey} = $value;
314 } else {
315 die "unable to parse config line: $line\n";
316 }
f76a2828
DM
317 next;
318 }
319 if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
320 my ($name, $value) = ($1, $2);
321 $data->{$name} = $value;
322 next;
323 }
54664cd3 324 if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
f76a2828
DM
325 my ($name, $value) = ($1, $2);
326
5b4657d0
DM
327 if ($lxc_array_configs->{$name}) {
328 $data->{$name} = [] if !defined($data->{$name});
329 push @{$data->{$name}}, parse_lxc_option($name, $value);
330 } else {
331 die "multiple definitions for $name\n" if defined($data->{$name});
332 $data->{$name} = parse_lxc_option($name, $value);
333 }
334
f76a2828
DM
335 next;
336 }
337
338 die "unable to parse config line: $line\n";
339 }
340
341 &$push_network($network);
342
343 foreach my $net (@{$network_list}) {
160f0941 344 next if $net->{type} eq 'empty'; # skip
f76a2828
DM
345 $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
346 $net->{hwaddr} = PVE::Tools::random_ether_addr() if !$net->{hwaddr};
347 die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
348
349 if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
350 $data->{"net$1"} = $net;
351 }
7dfc49cc
DM
352 }
353
354 return $data;
f76a2828
DM
355}
356
357sub config_list {
358 my $vmlist = PVE::Cluster::get_vmlist();
359 my $res = {};
360 return $res if !$vmlist || !$vmlist->{ids};
361 my $ids = $vmlist->{ids};
362
363 foreach my $vmid (keys %$ids) {
364 next if !$vmid; # skip CT0
365 my $d = $ids->{$vmid};
366 next if !$d->{node} || $d->{node} ne $nodename;
367 next if !$d->{type} || $d->{type} ne 'lxc';
368 $res->{$vmid}->{type} = 'lxc';
369 }
370 return $res;
371}
372
373sub cfs_config_path {
374 my ($vmid, $node) = @_;
375
376 $node = $nodename if !$node;
377 return "nodes/$node/lxc/$vmid/config";
378}
379
9c2d4ce9
DM
380sub config_file {
381 my ($vmid, $node) = @_;
382
383 my $cfspath = cfs_config_path($vmid, $node);
384 return "/etc/pve/$cfspath";
385}
386
f76a2828
DM
387sub load_config {
388 my ($vmid) = @_;
389
390 my $cfspath = cfs_config_path($vmid);
391
392 my $conf = PVE::Cluster::cfs_read_file($cfspath);
393 die "container $vmid does not exists\n" if !defined($conf);
394
395 return $conf;
396}
397
5b4657d0
DM
398sub create_config {
399 my ($vmid, $conf) = @_;
400
401 my $dir = "/etc/pve/nodes/$nodename/lxc";
402 mkdir $dir;
403
404 $dir .= "/$vmid";
405 mkdir($dir) || die "unable to create container configuration directory - $!\n";
406
407 write_config($vmid, $conf);
408}
409
410sub destroy_config {
411 my ($vmid) = @_;
412
413 my $dir = "/etc/pve/nodes/$nodename/lxc/$vmid";
414 File::Path::rmtree($dir);
415}
416
f76a2828
DM
417sub write_config {
418 my ($vmid, $conf) = @_;
419
420 my $cfspath = cfs_config_path($vmid);
421
422 PVE::Cluster::cfs_write_file($cfspath, $conf);
423}
424
9c2d4ce9
DM
425my $tempcounter = 0;
426sub write_temp_config {
427 my ($vmid, $conf) = @_;
7dfc49cc 428
9c2d4ce9
DM
429 $tempcounter++;
430 my $filename = "/tmp/temp-lxc-conf-$vmid-$$-$tempcounter.conf";
431
432 my $raw = write_lxc_config($filename, $conf);
433
434 PVE::Tools::file_set_contents($filename, $raw);
7dfc49cc 435
9c2d4ce9
DM
436 return $filename;
437}
438
d14a9a1b
DM
439# flock: we use one file handle per process, so lock file
440# can be called multiple times and succeeds for the same process.
441
442my $lock_handles = {};
443my $lockdir = "/run/lock/lxc";
444
445sub lock_filename {
446 my ($vmid) = @_;
447
448 return "$lockdir/pve-config-{$vmid}.lock";
449}
450
451sub lock_aquire {
452 my ($vmid, $timeout) = @_;
453
454 $timeout = 10 if !$timeout;
455 my $mode = LOCK_EX;
456
457 my $filename = lock_filename($vmid);
458
f99e8278
AD
459 mkdir $lockdir if !-d $lockdir;
460
d14a9a1b
DM
461 my $lock_func = sub {
462 if (!$lock_handles->{$$}->{$filename}) {
463 my $fh = new IO::File(">>$filename") ||
464 die "can't open file - $!\n";
465 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
466 }
467
468 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
469 print STDERR "trying to aquire lock...";
470 my $success;
471 while(1) {
472 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
473 # try again on EINTR (see bug #273)
474 if ($success || ($! != EINTR)) {
475 last;
476 }
477 }
478 if (!$success) {
479 print STDERR " failed\n";
480 die "can't aquire lock - $!\n";
481 }
482
483 $lock_handles->{$$}->{$filename}->{refcount}++;
484
485 print STDERR " OK\n";
486 }
487 };
488
489 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
490 my $err = $@;
491 if ($err) {
492 die "can't lock file '$filename' - $err";
493 }
494}
495
496sub lock_release {
497 my ($vmid) = @_;
498
499 my $filename = lock_filename($vmid);
500
501 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
502 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
503 if ($refcount <= 0) {
504 $lock_handles->{$$}->{$filename} = undef;
505 close ($fh);
506 }
507 }
508}
509
f76a2828
DM
510sub lock_container {
511 my ($vmid, $timeout, $code, @param) = @_;
512
d14a9a1b 513 my $res;
f76a2828 514
d14a9a1b
DM
515 lock_aquire($vmid, $timeout);
516 eval { $res = &$code(@param) };
517 my $err = $@;
518 lock_release($vmid);
f76a2828 519
d14a9a1b 520 die $err if $err;
f76a2828
DM
521
522 return $res;
523}
524
525my $confdesc = {
526 onboot => {
527 optional => 1,
528 type => 'boolean',
529 description => "Specifies whether a VM will be started during system bootup.",
530 default => 0,
531 },
a3249355 532 startup => get_standard_option('pve-startup-order'),
45573f7c 533 cpulimit => {
f76a2828 534 optional => 1,
8daf6a1c 535 type => 'number',
45573f7c 536 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.",
44da0641
DM
537 minimum => 0,
538 maximum => 128,
539 default => 0,
f76a2828
DM
540 },
541 cpuunits => {
542 optional => 1,
543 type => 'integer',
544 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.",
545 minimum => 0,
546 maximum => 500000,
547 default => 1000,
548 },
549 memory => {
550 optional => 1,
551 type => 'integer',
552 description => "Amount of RAM for the VM in MB.",
553 minimum => 16,
554 default => 512,
555 },
556 swap => {
557 optional => 1,
558 type => 'integer',
559 description => "Amount of SWAP for the VM in MB.",
560 minimum => 0,
561 default => 512,
562 },
563 disk => {
564 optional => 1,
565 type => 'number',
566 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
567 minimum => 0,
10fc3ba5 568 default => 4,
f76a2828
DM
569 },
570 hostname => {
571 optional => 1,
572 description => "Set a host name for the container.",
573 type => 'string',
574 maxLength => 255,
575 },
576 description => {
577 optional => 1,
578 type => 'string',
579 description => "Container description. Only used on the configuration web interface.",
580 },
581 searchdomain => {
582 optional => 1,
583 type => 'string',
584 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
585 },
586 nameserver => {
587 optional => 1,
588 type => 'string',
589 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.",
590 },
ec52ac21
DM
591};
592
593my $MAX_LXC_NETWORKS = 10;
594for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
595 $confdesc->{"net$i"} = {
f76a2828
DM
596 optional => 1,
597 type => 'string', format => 'pve-lxc-network',
598 description => "Specifies network interfaces for the container.",
7dfc49cc 599 };
ec52ac21
DM
600}
601
602sub option_exists {
603 my ($name) = @_;
604
605 return defined($confdesc->{$name});
606}
f76a2828
DM
607
608# add JSON properties for create and set function
609sub json_config_properties {
610 my $prop = shift;
611
612 foreach my $opt (keys %$confdesc) {
613 $prop->{$opt} = $confdesc->{$opt};
614 }
615
616 return $prop;
617}
618
822de0c3
DM
619# container status helpers
620
621sub list_active_containers {
622
623 my $filename = "/proc/net/unix";
624
625 # similar test is used by lcxcontainers.c: list_active_containers
626 my $res = {};
627
628 my $fh = IO::File->new ($filename, "r");
629 return $res if !$fh;
630
631 while (defined(my $line = <$fh>)) {
632 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
633 my $path = $1;
634 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
635 $res->{$1} = 1;
636 }
637 }
638 }
639
640 close($fh);
641
642 return $res;
643}
f76a2828 644
5c752bbf
DM
645# warning: this is slow
646sub check_running {
647 my ($vmid) = @_;
648
649 my $active_hash = list_active_containers();
650
651 return 1 if defined($active_hash->{$vmid});
652
653 return undef;
654}
655
10fc3ba5
DM
656sub get_container_disk_usage {
657 my ($vmid) = @_;
658
659 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
660
661 my $res = {
662 total => 0,
663 used => 0,
664 avail => 0,
665 };
666
667 my $parser = sub {
668 my $line = shift;
669 if (my ($fsid, $total, $used, $avail) = $line =~
670 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
671 $res = {
672 total => $total,
673 used => $used,
674 avail => $avail,
675 };
676 }
677 };
678 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
679 warn $@ if $@;
680
681 return $res;
682}
683
f76a2828
DM
684sub vmstatus {
685 my ($opt_vmid) = @_;
686
687 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
688
822de0c3
DM
689 my $active_hash = list_active_containers();
690
f76a2828 691 foreach my $vmid (keys %$list) {
f76a2828 692 my $d = $list->{$vmid};
10fc3ba5
DM
693
694 my $running = defined($active_hash->{$vmid});
695
696 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
697
698 my $cfspath = cfs_config_path($vmid);
238a56cb
DM
699 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
700
701 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
702 $d->{name} =~ s/[\s]//g;
e901d418 703
44da0641
DM
704 $d->{cpus} = 0;
705
706 my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
707 my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
708
709 if ($cfs_period_us && $cfs_quota_us) {
710 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
711 }
238a56cb
DM
712
713 $d->{disk} = 0;
10fc3ba5
DM
714 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
715 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
716
238a56cb 717 if (my $private = $conf->{'lxc.rootfs'}) {
10fc3ba5
DM
718 if ($private =~ m!^/!) {
719 my $res = PVE::Tools::df($private, 2);
720 $d->{disk} = $res->{used};
721 $d->{maxdisk} = $res->{total};
722 } elsif ($running) {
6ed8c6dd 723 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
10fc3ba5
DM
724 my $res = get_container_disk_usage($vmid);
725 $d->{disk} = $res->{used};
726 $d->{maxdisk} = $res->{total};
727 }
728 }
238a56cb
DM
729 }
730
731 $d->{mem} = 0;
732 $d->{swap} = 0;
733 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
44da0641 734 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
e901d418 735
238a56cb
DM
736 $d->{uptime} = 0;
737 $d->{cpu} = 0;
e901d418 738
238a56cb
DM
739 $d->{netout} = 0;
740 $d->{netin} = 0;
f76a2828 741
238a56cb
DM
742 $d->{diskread} = 0;
743 $d->{diskwrite} = 0;
f76a2828 744 }
238a56cb
DM
745
746 foreach my $vmid (keys %$list) {
747 my $d = $list->{$vmid};
748 next if $d->{status} ne 'running';
f76a2828 749
22a77285
DM
750 $d->{uptime} = 100; # fixme:
751
238a56cb
DM
752 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
753 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
754 }
755
f76a2828
DM
756 return $list;
757}
758
7dfc49cc
DM
759
760sub print_lxc_network {
f76a2828
DM
761 my $net = shift;
762
8a04b6c7 763 die "no network bridge defined\n" if !$net->{bridge};
f76a2828 764
8a04b6c7 765 my $res = "bridge=$net->{bridge}";
7dfc49cc 766
2b1fc2ea 767 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
768 next if !defined($net->{$k});
769 $res .= ",$k=$net->{$k}";
770 }
7dfc49cc 771
f76a2828
DM
772 return $res;
773}
774
7dfc49cc
DM
775sub parse_lxc_network {
776 my ($data) = @_;
777
778 my $res = {};
779
780 return $res if !$data;
781
782 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 783 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
784 $res->{$1} = $2;
785 } else {
786 return undef;
787 }
788 }
789
790 $res->{type} = 'veth';
791 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
792
793 return $res;
794}
f76a2828 795
238a56cb
DM
796sub read_cgroup_value {
797 my ($group, $vmid, $name, $full) = @_;
798
799 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
800
801 return PVE::Tools::file_get_contents($path) if $full;
802
803 return PVE::Tools::file_read_firstline($path);
804}
805
52f1d76b
DM
806sub find_lxc_console_pids {
807
808 my $res = {};
809
810 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
811 my ($pid) = @_;
812
813 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
814 return if !$cmdline;
815
816 my @args = split(/\0/, $cmdline);
817
818 # serach for lxc-console -n <vmid>
819 return if scalar(@args) != 3;
820 return if $args[1] ne '-n';
821 return if $args[2] !~ m/^\d+$/;
822 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
823
824 my $vmid = $args[2];
825
826 push @{$res->{$vmid}}, $pid;
827 });
828
829 return $res;
830}
831
55fa4e09
DM
832my $ipv4_reverse_mask = [
833 '0.0.0.0',
834 '128.0.0.0',
835 '192.0.0.0',
836 '224.0.0.0',
837 '240.0.0.0',
838 '248.0.0.0',
839 '252.0.0.0',
840 '254.0.0.0',
841 '255.0.0.0',
842 '255.128.0.0',
843 '255.192.0.0',
844 '255.224.0.0',
845 '255.240.0.0',
846 '255.248.0.0',
847 '255.252.0.0',
848 '255.254.0.0',
849 '255.255.0.0',
850 '255.255.128.0',
851 '255.255.192.0',
852 '255.255.224.0',
853 '255.255.240.0',
854 '255.255.248.0',
855 '255.255.252.0',
856 '255.255.254.0',
857 '255.255.255.0',
858 '255.255.255.128',
859 '255.255.255.192',
860 '255.255.255.224',
861 '255.255.255.240',
862 '255.255.255.248',
863 '255.255.255.252',
864 '255.255.255.254',
865 '255.255.255.255',
866];
867
868# Note: we cannot use Net:IP, because that only allows strict
869# CIDR networks
870sub parse_ipv4_cidr {
871 my ($cidr, $noerr) = @_;
872
873 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
874 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
875 }
876
877 return undef if $noerr;
878
879 die "unable to parse ipv4 address/mask\n";
880}
93285df8 881
b80dd50a
DM
882sub lxc_conf_to_pve {
883 my ($vmid, $lxc_conf) = @_;
884
885 my $properties = json_config_properties();
886
887 my $conf = { digest => $lxc_conf->{digest} };
888
889 foreach my $k (keys %$properties) {
890
891 if ($k eq 'description') {
892 if (my $raw = $lxc_conf->{'pve.comment'}) {
893 $conf->{$k} = PVE::Tools::decode_text($raw);
894 }
a99b3509
DM
895 } elsif ($k eq 'onboot') {
896 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
a3249355
DM
897 } elsif ($k eq 'startup') {
898 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
b80dd50a
DM
899 } elsif ($k eq 'hostname') {
900 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
ffa1d001
DM
901 } elsif ($k eq 'nameserver') {
902 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
903 } elsif ($k eq 'searchdomain') {
904 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
b80dd50a
DM
905 } elsif ($k eq 'memory') {
906 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
907 $conf->{$k} = int($value / (1024*1024));
908 }
909 } elsif ($k eq 'swap') {
910 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
911 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
912 $conf->{$k} = int(($value -$mem) / (1024*1024));
913 }
45573f7c 914 } elsif ($k eq 'cpulimit') {
b80dd50a
DM
915 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
916 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
917
918 if ($cfs_period_us && $cfs_quota_us) {
8daf6a1c 919 $conf->{$k} = $cfs_quota_us/$cfs_period_us;
b80dd50a
DM
920 } else {
921 $conf->{$k} = 0;
922 }
923 } elsif ($k eq 'cpuunits') {
924 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
10fc3ba5
DM
925 } elsif ($k eq 'disk') {
926 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
927 $lxc_conf->{'pve.disksize'} : 0;
b80dd50a
DM
928 } elsif ($k =~ m/^net\d$/) {
929 my $net = $lxc_conf->{$k};
930 next if !$net;
931 $conf->{$k} = print_lxc_network($net);
932 }
933 }
934
935 return $conf;
936}
937
117636e5
DM
938# verify and cleanup nameserver list (replace \0 with ' ')
939sub verify_nameserver_list {
940 my ($nameserver_list) = @_;
941
942 my @list = ();
943 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
944 PVE::JSONSchema::pve_verify_ip($server);
945 push @list, $server;
946 }
947
948 return join(' ', @list);
949}
950
951sub verify_searchdomain_list {
952 my ($searchdomain_list) = @_;
953
954 my @list = ();
955 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
956 # todo: should we add checks for valid dns domains?
957 push @list, $server;
958 }
959
960 return join(' ', @list);
961}
962
93285df8
DM
963sub update_lxc_config {
964 my ($vmid, $conf, $running, $param, $delete) = @_;
965
966 # fixme: hotplug
967 die "unable to modify config while container is running\n" if $running;
968
969 if (defined($delete)) {
970 foreach my $opt (@$delete) {
971 if ($opt eq 'hostname' || $opt eq 'memory') {
972 die "unable to delete required option '$opt'\n";
973 } elsif ($opt eq 'swap') {
44da0641 974 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
93285df8
DM
975 } elsif ($opt eq 'description') {
976 delete $conf->{'pve.comment'};
a99b3509
DM
977 } elsif ($opt eq 'onboot') {
978 delete $conf->{'pve.onboot'};
a3249355
DM
979 } elsif ($opt eq 'startup') {
980 delete $conf->{'pve.startup'};
ffa1d001
DM
981 } elsif ($opt eq 'nameserver') {
982 delete $conf->{'pve.nameserver'};
983 } elsif ($opt eq 'searchdomain') {
984 delete $conf->{'pve.searchdomain'};
93285df8
DM
985 } elsif ($opt =~ m/^net\d$/) {
986 delete $conf->{$opt};
987 } else {
988 die "implement me"
989 }
990 }
991 }
992
993 foreach my $opt (keys %$param) {
994 my $value = $param->{$opt};
995 if ($opt eq 'hostname') {
996 $conf->{'lxc.utsname'} = $value;
a99b3509
DM
997 } elsif ($opt eq 'onboot') {
998 $conf->{'pve.onboot'} = $value ? 1 : 0;
a3249355
DM
999 } elsif ($opt eq 'startup') {
1000 $conf->{'pve.startup'} = $value;
ffa1d001 1001 } elsif ($opt eq 'nameserver') {
117636e5 1002 my $list = verify_nameserver_list($value);
c325b32f 1003 $conf->{'pve.nameserver'} = $list;
ffa1d001 1004 } elsif ($opt eq 'searchdomain') {
117636e5 1005 my $list = verify_searchdomain_list($value);
c325b32f 1006 $conf->{'pve.searchdomain'} = $list;
93285df8
DM
1007 } elsif ($opt eq 'memory') {
1008 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
1009 } elsif ($opt eq 'swap') {
44da0641
DM
1010 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
1011 $mem = $param->{memory}*1024*1024 if $param->{memory};
1012 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
45573f7c 1013 } elsif ($opt eq 'cpulimit') {
44da0641
DM
1014 if ($value > 0) {
1015 my $cfs_period_us = 100000;
1016 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
1017 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
1018 } else {
1019 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
1020 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1021 }
b80dd50a
DM
1022 } elsif ($opt eq 'cpuunits') {
1023 $conf->{'lxc.cgroup.cpu.shares'} = $value;
93285df8
DM
1024 } elsif ($opt eq 'description') {
1025 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
10fc3ba5
DM
1026 } elsif ($opt eq 'disk') {
1027 $conf->{'pve.disksize'} = $value;
93285df8
DM
1028 } elsif ($opt =~ m/^net(\d+)$/) {
1029 my $netid = $1;
1030 my $net = PVE::LXC::parse_lxc_network($value);
1031 $net->{'veth.pair'} = "veth${vmid}.$netid";
1032 $conf->{$opt} = $net;
1033 } else {
1034 die "implement me"
1035 }
1036 }
1037}
c325b32f
DM
1038
1039sub get_primary_ips {
1040 my ($conf) = @_;
1041
1042 # return data from net0
1043
1044 my $net = $conf->{net0};
1045 return undef if !$net;
1046
1047 my $ipv4 = $net->{ip};
1048 $ipv4 =~ s!/\d+$!! if $ipv4;
1049 my $ipv6 = $net->{ip};
1050 $ipv6 =~ s!/\d+$!! if $ipv6;
1051
1052 return ($ipv4, $ipv6);
1053}
148d1cb4
DM
1054
1055sub destory_lxc_container {
1056 my ($storage_cfg, $vmid, $conf) = @_;
1057
1058 if (my $volid = $conf->{'pve.volid'}) {
1059
1060 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
1061 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1062
1063 PVE::Storage::vdisk_free($storage_cfg, $volid);
1064
1065 destroy_config($vmid);
1066
1067 } else {
1068 my $cmd = ['lxc-destroy', '-n', $vmid ];
1069
1070 PVE::Tools::run_command($cmd);
1071 }
1072}
238a56cb 1073
f76a2828 10741;