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