]> git.proxmox.com Git - pve-container.git/blob - src/PVE/LXC.pm
implement restore command
[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 my $lock_func = sub {
441 if (!$lock_handles->{$$}->{$filename}) {
442 my $fh = new IO::File(">>$filename") ||
443 die "can't open file - $!\n";
444 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
445 }
446
447 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
448 print STDERR "trying to aquire lock...";
449 my $success;
450 while(1) {
451 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
452 # try again on EINTR (see bug #273)
453 if ($success || ($! != EINTR)) {
454 last;
455 }
456 }
457 if (!$success) {
458 print STDERR " failed\n";
459 die "can't aquire lock - $!\n";
460 }
461
462 $lock_handles->{$$}->{$filename}->{refcount}++;
463
464 print STDERR " OK\n";
465 }
466 };
467
468 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
469 my $err = $@;
470 if ($err) {
471 die "can't lock file '$filename' - $err";
472 }
473 }
474
475 sub lock_release {
476 my ($vmid) = @_;
477
478 my $filename = lock_filename($vmid);
479
480 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
481 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
482 if ($refcount <= 0) {
483 $lock_handles->{$$}->{$filename} = undef;
484 close ($fh);
485 }
486 }
487 }
488
489 sub lock_container {
490 my ($vmid, $timeout, $code, @param) = @_;
491
492 my $res;
493
494 lock_aquire($vmid, $timeout);
495 eval { $res = &$code(@param) };
496 my $err = $@;
497 lock_release($vmid);
498
499 die $err if $err;
500
501 return $res;
502 }
503
504 my $confdesc = {
505 onboot => {
506 optional => 1,
507 type => 'boolean',
508 description => "Specifies whether a VM will be started during system bootup.",
509 default => 0,
510 },
511 startup => get_standard_option('pve-startup-order'),
512 cpus => {
513 optional => 1,
514 type => 'integer',
515 description => "The number of CPUs for this container (0 is unlimited).",
516 minimum => 0,
517 maximum => 128,
518 default => 0,
519 },
520 cpuunits => {
521 optional => 1,
522 type => 'integer',
523 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.",
524 minimum => 0,
525 maximum => 500000,
526 default => 1000,
527 },
528 memory => {
529 optional => 1,
530 type => 'integer',
531 description => "Amount of RAM for the VM in MB.",
532 minimum => 16,
533 default => 512,
534 },
535 swap => {
536 optional => 1,
537 type => 'integer',
538 description => "Amount of SWAP for the VM in MB.",
539 minimum => 0,
540 default => 512,
541 },
542 disk => {
543 optional => 1,
544 type => 'number',
545 description => "Amount of disk space for the VM in GB. A zero indicates no limits.",
546 minimum => 0,
547 default => 4,
548 },
549 hostname => {
550 optional => 1,
551 description => "Set a host name for the container.",
552 type => 'string',
553 maxLength => 255,
554 },
555 description => {
556 optional => 1,
557 type => 'string',
558 description => "Container description. Only used on the configuration web interface.",
559 },
560 searchdomain => {
561 optional => 1,
562 type => 'string',
563 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
564 },
565 nameserver => {
566 optional => 1,
567 type => 'string',
568 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.",
569 },
570 };
571
572 my $MAX_LXC_NETWORKS = 10;
573 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
574 $confdesc->{"net$i"} = {
575 optional => 1,
576 type => 'string', format => 'pve-lxc-network',
577 description => "Specifies network interfaces for the container.",
578 };
579 }
580
581 sub option_exists {
582 my ($name) = @_;
583
584 return defined($confdesc->{$name});
585 }
586
587 # add JSON properties for create and set function
588 sub json_config_properties {
589 my $prop = shift;
590
591 foreach my $opt (keys %$confdesc) {
592 $prop->{$opt} = $confdesc->{$opt};
593 }
594
595 return $prop;
596 }
597
598 # container status helpers
599
600 sub list_active_containers {
601
602 my $filename = "/proc/net/unix";
603
604 # similar test is used by lcxcontainers.c: list_active_containers
605 my $res = {};
606
607 my $fh = IO::File->new ($filename, "r");
608 return $res if !$fh;
609
610 while (defined(my $line = <$fh>)) {
611 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
612 my $path = $1;
613 if ($path =~ m!^@/etc/pve/lxc/(\d+)/command$!) {
614 $res->{$1} = 1;
615 }
616 }
617 }
618
619 close($fh);
620
621 return $res;
622 }
623
624 # warning: this is slow
625 sub check_running {
626 my ($vmid) = @_;
627
628 my $active_hash = list_active_containers();
629
630 return 1 if defined($active_hash->{$vmid});
631
632 return undef;
633 }
634
635 sub get_container_disk_usage {
636 my ($vmid) = @_;
637
638 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
639
640 my $res = {
641 total => 0,
642 used => 0,
643 avail => 0,
644 };
645
646 my $parser = sub {
647 my $line = shift;
648 if (my ($fsid, $total, $used, $avail) = $line =~
649 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
650 $res = {
651 total => $total,
652 used => $used,
653 avail => $avail,
654 };
655 }
656 };
657 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
658 warn $@ if $@;
659
660 return $res;
661 }
662
663 sub vmstatus {
664 my ($opt_vmid) = @_;
665
666 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
667
668 my $active_hash = list_active_containers();
669
670 foreach my $vmid (keys %$list) {
671 my $d = $list->{$vmid};
672
673 my $running = defined($active_hash->{$vmid});
674
675 $d->{status} = $running ? 'running' : 'stopped';
676
677 my $cfspath = cfs_config_path($vmid);
678 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
679
680 $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
681 $d->{name} =~ s/[\s]//g;
682
683 $d->{cpus} = 0;
684
685 my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
686 my $cfs_quota_us = $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
687
688 if ($cfs_period_us && $cfs_quota_us) {
689 $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
690 }
691
692 $d->{disk} = 0;
693 $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
694 int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
695
696 if (my $private = $conf->{'lxc.rootfs'}) {
697 if ($private =~ m!^/!) {
698 my $res = PVE::Tools::df($private, 2);
699 $d->{disk} = $res->{used};
700 $d->{maxdisk} = $res->{total};
701 } elsif ($running) {
702 if ($private =~ m!^(?:loop|nbd):(?:\S+)$!) {
703 my $res = get_container_disk_usage($vmid);
704 $d->{disk} = $res->{used};
705 $d->{maxdisk} = $res->{total};
706 }
707 }
708 }
709
710 $d->{mem} = 0;
711 $d->{swap} = 0;
712 $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
713 ($conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}||0);
714
715 $d->{uptime} = 0;
716 $d->{cpu} = 0;
717
718 $d->{netout} = 0;
719 $d->{netin} = 0;
720
721 $d->{diskread} = 0;
722 $d->{diskwrite} = 0;
723 }
724
725 foreach my $vmid (keys %$list) {
726 my $d = $list->{$vmid};
727 next if $d->{status} ne 'running';
728
729 $d->{uptime} = 100; # fixme:
730
731 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
732 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
733 }
734
735 return $list;
736 }
737
738
739 sub print_lxc_network {
740 my $net = shift;
741
742 die "no network bridge defined\n" if !$net->{bridge};
743
744 my $res = "bridge=$net->{bridge}";
745
746 foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
747 next if !defined($net->{$k});
748 $res .= ",$k=$net->{$k}";
749 }
750
751 return $res;
752 }
753
754 sub parse_lxc_network {
755 my ($data) = @_;
756
757 my $res = {};
758
759 return $res if !$data;
760
761 foreach my $pv (split (/,/, $data)) {
762 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
763 $res->{$1} = $2;
764 } else {
765 return undef;
766 }
767 }
768
769 $res->{type} = 'veth';
770 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{mac};
771
772 return $res;
773 }
774
775 sub read_cgroup_value {
776 my ($group, $vmid, $name, $full) = @_;
777
778 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
779
780 return PVE::Tools::file_get_contents($path) if $full;
781
782 return PVE::Tools::file_read_firstline($path);
783 }
784
785 sub find_lxc_console_pids {
786
787 my $res = {};
788
789 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
790 my ($pid) = @_;
791
792 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
793 return if !$cmdline;
794
795 my @args = split(/\0/, $cmdline);
796
797 # serach for lxc-console -n <vmid>
798 return if scalar(@args) != 3;
799 return if $args[1] ne '-n';
800 return if $args[2] !~ m/^\d+$/;
801 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
802
803 my $vmid = $args[2];
804
805 push @{$res->{$vmid}}, $pid;
806 });
807
808 return $res;
809 }
810
811 my $ipv4_reverse_mask = [
812 '0.0.0.0',
813 '128.0.0.0',
814 '192.0.0.0',
815 '224.0.0.0',
816 '240.0.0.0',
817 '248.0.0.0',
818 '252.0.0.0',
819 '254.0.0.0',
820 '255.0.0.0',
821 '255.128.0.0',
822 '255.192.0.0',
823 '255.224.0.0',
824 '255.240.0.0',
825 '255.248.0.0',
826 '255.252.0.0',
827 '255.254.0.0',
828 '255.255.0.0',
829 '255.255.128.0',
830 '255.255.192.0',
831 '255.255.224.0',
832 '255.255.240.0',
833 '255.255.248.0',
834 '255.255.252.0',
835 '255.255.254.0',
836 '255.255.255.0',
837 '255.255.255.128',
838 '255.255.255.192',
839 '255.255.255.224',
840 '255.255.255.240',
841 '255.255.255.248',
842 '255.255.255.252',
843 '255.255.255.254',
844 '255.255.255.255',
845 ];
846
847 # Note: we cannot use Net:IP, because that only allows strict
848 # CIDR networks
849 sub parse_ipv4_cidr {
850 my ($cidr, $noerr) = @_;
851
852 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
853 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
854 }
855
856 return undef if $noerr;
857
858 die "unable to parse ipv4 address/mask\n";
859 }
860
861 sub lxc_conf_to_pve {
862 my ($vmid, $lxc_conf) = @_;
863
864 my $properties = json_config_properties();
865
866 my $conf = { digest => $lxc_conf->{digest} };
867
868 foreach my $k (keys %$properties) {
869
870 if ($k eq 'description') {
871 if (my $raw = $lxc_conf->{'pve.comment'}) {
872 $conf->{$k} = PVE::Tools::decode_text($raw);
873 }
874 } elsif ($k eq 'onboot') {
875 $conf->{$k} = $lxc_conf->{'pve.onboot'} if $lxc_conf->{'pve.onboot'};
876 } elsif ($k eq 'startup') {
877 $conf->{$k} = $lxc_conf->{'pve.startup'} if $lxc_conf->{'pve.startup'};
878 } elsif ($k eq 'hostname') {
879 $conf->{$k} = $lxc_conf->{'lxc.utsname'} if $lxc_conf->{'lxc.utsname'};
880 } elsif ($k eq 'nameserver') {
881 $conf->{$k} = $lxc_conf->{'pve.nameserver'} if $lxc_conf->{'pve.nameserver'};
882 } elsif ($k eq 'searchdomain') {
883 $conf->{$k} = $lxc_conf->{'pve.searchdomain'} if $lxc_conf->{'pve.searchdomain'};
884 } elsif ($k eq 'memory') {
885 if (my $value = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'}) {
886 $conf->{$k} = int($value / (1024*1024));
887 }
888 } elsif ($k eq 'swap') {
889 if (my $value = $lxc_conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'}) {
890 my $mem = $lxc_conf->{'lxc.cgroup.memory.limit_in_bytes'} || 0;
891 $conf->{$k} = int(($value -$mem) / (1024*1024));
892 }
893 } elsif ($k eq 'cpus') {
894 my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
895 my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
896
897 if ($cfs_period_us && $cfs_quota_us) {
898 $conf->{$k} = int($cfs_quota_us/$cfs_period_us);
899 } else {
900 $conf->{$k} = 0;
901 }
902 } elsif ($k eq 'cpuunits') {
903 $conf->{$k} = $lxc_conf->{'lxc.cgroup.cpu.shares'} || 1024;
904 } elsif ($k eq 'disk') {
905 $conf->{$k} = defined($lxc_conf->{'pve.disksize'}) ?
906 $lxc_conf->{'pve.disksize'} : 0;
907 } elsif ($k =~ m/^net\d$/) {
908 my $net = $lxc_conf->{$k};
909 next if !$net;
910 $conf->{$k} = print_lxc_network($net);
911 }
912 }
913
914 return $conf;
915 }
916
917 # verify and cleanup nameserver list (replace \0 with ' ')
918 sub verify_nameserver_list {
919 my ($nameserver_list) = @_;
920
921 my @list = ();
922 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
923 PVE::JSONSchema::pve_verify_ip($server);
924 push @list, $server;
925 }
926
927 return join(' ', @list);
928 }
929
930 sub verify_searchdomain_list {
931 my ($searchdomain_list) = @_;
932
933 my @list = ();
934 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
935 # todo: should we add checks for valid dns domains?
936 push @list, $server;
937 }
938
939 return join(' ', @list);
940 }
941
942 sub update_lxc_config {
943 my ($vmid, $conf, $running, $param, $delete) = @_;
944
945 # fixme: hotplug
946 die "unable to modify config while container is running\n" if $running;
947
948 if (defined($delete)) {
949 foreach my $opt (@$delete) {
950 if ($opt eq 'hostname' || $opt eq 'memory') {
951 die "unable to delete required option '$opt'\n";
952 } elsif ($opt eq 'swap') {
953 delete $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'};
954 } elsif ($opt eq 'description') {
955 delete $conf->{'pve.comment'};
956 } elsif ($opt eq 'onboot') {
957 delete $conf->{'pve.onboot'};
958 } elsif ($opt eq 'startup') {
959 delete $conf->{'pve.startup'};
960 } elsif ($opt eq 'nameserver') {
961 delete $conf->{'pve.nameserver'};
962 } elsif ($opt eq 'searchdomain') {
963 delete $conf->{'pve.searchdomain'};
964 } elsif ($opt =~ m/^net\d$/) {
965 delete $conf->{$opt};
966 } else {
967 die "implement me"
968 }
969 }
970 }
971
972 foreach my $opt (keys %$param) {
973 my $value = $param->{$opt};
974 if ($opt eq 'hostname') {
975 $conf->{'lxc.utsname'} = $value;
976 } elsif ($opt eq 'onboot') {
977 $conf->{'pve.onboot'} = $value ? 1 : 0;
978 } elsif ($opt eq 'startup') {
979 $conf->{'pve.startup'} = $value;
980 } elsif ($opt eq 'nameserver') {
981 my $list = verify_nameserver_list($value);
982 $conf->{'pve.nameserver'} = $list;
983 } elsif ($opt eq 'searchdomain') {
984 my $list = verify_searchdomain_list($value);
985 $conf->{'pve.searchdomain'} = $list;
986 } elsif ($opt eq 'memory') {
987 $conf->{'lxc.cgroup.memory.limit_in_bytes'} = $value*1024*1024;
988 } elsif ($opt eq 'swap') {
989 my $mem = $conf->{'lxc.cgroup.memory.limit_in_bytes'};
990 $mem = $param->{memory}*1024*1024 if $param->{memory};
991 $conf->{'lxc.cgroup.memory.memsw.limit_in_bytes'} = $mem + $value*1024*1024;
992 } elsif ($opt eq 'cpus') {
993 if ($value > 0) {
994 my $cfs_period_us = 100000;
995 $conf->{'lxc.cgroup.cpu.cfs_period_us'} = $cfs_period_us;
996 $conf->{'lxc.cgroup.cpu.cfs_quota_us'} = $cfs_period_us*$value;
997 } else {
998 delete $conf->{'lxc.cgroup.cpu.cfs_period_us'};
999 delete $conf->{'lxc.cgroup.cpu.cfs_quota_us'};
1000 }
1001 } elsif ($opt eq 'cpuunits') {
1002 $conf->{'lxc.cgroup.cpu.shares'} = $value;
1003 } elsif ($opt eq 'description') {
1004 $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
1005 } elsif ($opt eq 'disk') {
1006 $conf->{'pve.disksize'} = $value;
1007 } elsif ($opt =~ m/^net(\d+)$/) {
1008 my $netid = $1;
1009 my $net = PVE::LXC::parse_lxc_network($value);
1010 $net->{'veth.pair'} = "veth${vmid}.$netid";
1011 $conf->{$opt} = $net;
1012 } else {
1013 die "implement me"
1014 }
1015 }
1016 }
1017
1018 sub get_primary_ips {
1019 my ($conf) = @_;
1020
1021 # return data from net0
1022
1023 my $net = $conf->{net0};
1024 return undef if !$net;
1025
1026 my $ipv4 = $net->{ip};
1027 $ipv4 =~ s!/\d+$!! if $ipv4;
1028 my $ipv6 = $net->{ip};
1029 $ipv6 =~ s!/\d+$!! if $ipv6;
1030
1031 return ($ipv4, $ipv6);
1032 }
1033
1034 sub destory_lxc_container {
1035 my ($storage_cfg, $vmid, $conf) = @_;
1036
1037 if (my $volid = $conf->{'pve.volid'}) {
1038
1039 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volid);
1040 die "got strange volid (containe is not owner!)\n" if $vmid != $owner;
1041
1042 PVE::Storage::vdisk_free($storage_cfg, $volid);
1043
1044 destroy_config($vmid);
1045
1046 } else {
1047 my $cmd = ['lxc-destroy', '-n', $vmid ];
1048
1049 PVE::Tools::run_command($cmd);
1050 }
1051 }
1052
1053 1;