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