]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
activate all mountpoints at vmstart
[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);
68fba17b 16use PVE::Network;
f76a2828
DM
17
18use Data::Dumper;
19
27916659
DM
20my $nodename = PVE::INotify::nodename();
21
22cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
f76a2828 23
7dfc49cc
DM
24PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
25sub verify_lxc_network {
26 my ($value, $noerr) = @_;
27
28 return $value if parse_lxc_network($value);
29
30 return undef if $noerr;
31
32 die "unable to parse network setting\n";
33}
34
27916659
DM
35PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
36sub verify_ct_mountpoint {
37 my ($value, $noerr) = @_;
822de0c3 38
27916659 39 return $value if parse_ct_mountpoint($value);
822de0c3 40
27916659 41 return undef if $noerr;
822de0c3 42
27916659 43 die "unable to parse CT mountpoint options\n";
822de0c3
DM
44}
45
27916659
DM
46PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
47 type => 'string', format => 'pve-ct-mountpoint',
48 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
8fbd2935 49 description => "Use volume as container root.",
27916659
DM
50 optional => 1,
51});
52
53my $confdesc = {
09d3ec42
DM
54 lock => {
55 optional => 1,
56 type => 'string',
57 description => "Lock/unlock the VM.",
58 enum => [qw(migrate backup snapshot rollback)],
59 },
27916659
DM
60 onboot => {
61 optional => 1,
62 type => 'boolean',
63 description => "Specifies whether a VM will be started during system bootup.",
64 default => 0,
117636e5 65 },
27916659 66 startup => get_standard_option('pve-startup-order'),
bb1ac2de
DM
67 template => {
68 optional => 1,
69 type => 'boolean',
70 description => "Enable/disable Template.",
71 default => 0,
72 },
27916659
DM
73 arch => {
74 optional => 1,
75 type => 'string',
76 enum => ['amd64', 'i386'],
77 description => "OS architecture type.",
78 default => 'amd64',
117636e5 79 },
27916659
DM
80 ostype => {
81 optional => 1,
82 type => 'string',
83 enum => ['debian', 'ubuntu', 'centos'],
84 description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
a3249355 85 },
4f958489
DM
86 console => {
87 optional => 1,
88 type => 'boolean',
89 description => "Attach a console device (/dev/console) to the container.",
90 default => 1,
91 },
27916659
DM
92 tty => {
93 optional => 1,
94 type => 'integer',
95 description => "Specify the number of tty available to the container",
96 minimum => 0,
97 maximum => 6,
0d0ca400 98 default => 2,
611fe3aa 99 },
27916659
DM
100 cpulimit => {
101 optional => 1,
102 type => 'number',
103 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.",
104 minimum => 0,
105 maximum => 128,
106 default => 0,
107 },
108 cpuunits => {
109 optional => 1,
110 type => 'integer',
111 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.",
112 minimum => 0,
113 maximum => 500000,
81bee809 114 default => 1024,
27916659
DM
115 },
116 memory => {
117 optional => 1,
118 type => 'integer',
119 description => "Amount of RAM for the VM in MB.",
120 minimum => 16,
121 default => 512,
122 },
123 swap => {
124 optional => 1,
125 type => 'integer',
126 description => "Amount of SWAP for the VM in MB.",
127 minimum => 0,
128 default => 512,
129 },
130 hostname => {
131 optional => 1,
132 description => "Set a host name for the container.",
133 type => 'string',
134 maxLength => 255,
135 },
136 description => {
137 optional => 1,
138 type => 'string',
139 description => "Container description. Only used on the configuration web interface.",
140 },
141 searchdomain => {
142 optional => 1,
143 type => 'string',
144 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
145 },
146 nameserver => {
147 optional => 1,
148 type => 'string',
149 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.",
150 },
151 rootfs => get_standard_option('pve-ct-rootfs'),
09d3ec42
DM
152 parent => {
153 optional => 1,
154 type => 'string', format => 'pve-configid',
155 maxLength => 40,
156 description => "Parent snapshot name. This is used internally, and should not be modified.",
157 },
158 snaptime => {
159 optional => 1,
160 description => "Timestamp for snapshots.",
161 type => 'integer',
162 minimum => 0,
163 },
f76a2828
DM
164};
165
e576f689
DM
166my $valid_lxc_conf_keys = {
167 'lxc.include' => 1,
168 'lxc.arch' => 1,
169 'lxc.utsname' => 1,
170 'lxc.haltsignal' => 1,
171 'lxc.rebootsignal' => 1,
172 'lxc.stopsignal' => 1,
173 'lxc.init_cmd' => 1,
174 'lxc.network.type' => 1,
175 'lxc.network.flags' => 1,
176 'lxc.network.link' => 1,
177 'lxc.network.mtu' => 1,
178 'lxc.network.name' => 1,
179 'lxc.network.hwaddr' => 1,
180 'lxc.network.ipv4' => 1,
181 'lxc.network.ipv4.gateway' => 1,
182 'lxc.network.ipv6' => 1,
183 'lxc.network.ipv6.gateway' => 1,
184 'lxc.network.script.up' => 1,
185 'lxc.network.script.down' => 1,
186 'lxc.pts' => 1,
187 'lxc.console.logfile' => 1,
188 'lxc.console' => 1,
189 'lxc.tty' => 1,
190 'lxc.devttydir' => 1,
191 'lxc.hook.autodev' => 1,
192 'lxc.autodev' => 1,
193 'lxc.kmsg' => 1,
194 'lxc.mount' => 1,
195 'lxc.mount.entry' => 1,
196 'lxc.mount.auto' => 1,
197 'lxc.rootfs' => 1,
198 'lxc.rootfs.mount' => 1,
199 'lxc.rootfs.options' => 1,
200 # lxc.cgroup.*
201 'lxc.cap.drop' => 1,
202 'lxc.cap.keep' => 1,
203 'lxc.aa_profile' => 1,
204 'lxc.aa_allow_incomplete' => 1,
205 'lxc.se_context' => 1,
206 'lxc.seccomp' => 1,
207 'lxc.id_map' => 1,
208 'lxc.hook.pre-start' => 1,
209 'lxc.hook.pre-mount' => 1,
210 'lxc.hook.mount' => 1,
211 'lxc.hook.start' => 1,
212 'lxc.hook.post-stop' => 1,
213 'lxc.hook.clone' => 1,
214 'lxc.hook.destroy' => 1,
215 'lxc.loglevel' => 1,
216 'lxc.logfile' => 1,
217 'lxc.start.auto' => 1,
218 'lxc.start.delay' => 1,
219 'lxc.start.order' => 1,
220 'lxc.group' => 1,
221 'lxc.environment' => 1,
222 'lxc.' => 1,
223 'lxc.' => 1,
224 'lxc.' => 1,
225 'lxc.' => 1,
226};
227
27916659
DM
228my $MAX_LXC_NETWORKS = 10;
229for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
230 $confdesc->{"net$i"} = {
231 optional => 1,
232 type => 'string', format => 'pve-lxc-network',
233 description => "Specifies network interfaces for the container.\n\n".
234 "The string should have the follow format:\n\n".
235 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
236 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
237 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
238 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
239 };
90bc31f7
DM
240}
241
02c9d10c
AD
242my $MAX_MOUNT_POINTS = 10;
243for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
244 $confdesc->{"mp$i"} = {
245 optional => 1,
246 type => 'string', format => 'pve-ct-mountpoint',
247 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
248 description => "Use volume as container mount point.",
249 optional => 1,
250 };
251}
252
27916659
DM
253sub write_pct_config {
254 my ($filename, $conf) = @_;
f76a2828 255
27916659 256 delete $conf->{snapstate}; # just to be sure
f76a2828 257
27916659
DM
258 my $generate_raw_config = sub {
259 my ($conf) = @_;
f76a2828 260
27916659 261 my $raw = '';
cbb03fea 262
27916659
DM
263 # add description as comment to top of file
264 my $descr = $conf->{description} || '';
265 foreach my $cl (split(/\n/, $descr)) {
266 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
a12a36e0 267 }
fff3a342 268
27916659 269 foreach my $key (sort keys %$conf) {
09d3ec42 270 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
e576f689 271 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
27916659 272 $raw .= "$key: $conf->{$key}\n";
a12a36e0 273 }
e576f689
DM
274
275 if (my $lxcconf = $conf->{lxc}) {
276 foreach my $entry (@$lxcconf) {
277 my ($k, $v) = @$entry;
278 $raw .= "$k: $v\n";
279 }
280 }
281
27916659 282 return $raw;
a12a36e0 283 };
160f0941 284
27916659 285 my $raw = &$generate_raw_config($conf);
a12a36e0 286
27916659
DM
287 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
288 $raw .= "\n[$snapname]\n";
289 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
f76a2828
DM
290 }
291
f76a2828
DM
292 return $raw;
293}
294
27916659
DM
295sub check_type {
296 my ($key, $value) = @_;
822de0c3 297
27916659 298 die "unknown setting '$key'\n" if !$confdesc->{$key};
822de0c3 299
27916659
DM
300 my $type = $confdesc->{$key}->{type};
301
302 if (!defined($value)) {
303 die "got undefined value\n";
304 }
305
306 if ($value =~ m/[\n\r]/) {
307 die "property contains a line feed\n";
308 }
822de0c3 309
27916659
DM
310 if ($type eq 'boolean') {
311 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
312 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
313 die "type check ('boolean') failed - got '$value'\n";
314 } elsif ($type eq 'integer') {
315 return int($1) if $value =~ m/^(\d+)$/;
316 die "type check ('integer') failed - got '$value'\n";
317 } elsif ($type eq 'number') {
318 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
319 die "type check ('number') failed - got '$value'\n";
320 } elsif ($type eq 'string') {
321 if (my $fmt = $confdesc->{$key}->{format}) {
322 PVE::JSONSchema::check_format($fmt, $value);
323 return $value;
324 }
cbb03fea 325 return $value;
822de0c3 326 } else {
27916659 327 die "internal error"
822de0c3 328 }
822de0c3
DM
329}
330
27916659 331sub parse_pct_config {
f76a2828
DM
332 my ($filename, $raw) = @_;
333
334 return undef if !defined($raw);
335
27916659 336 my $res = {
f76a2828 337 digest => Digest::SHA::sha1_hex($raw),
27916659 338 snapshots => {},
f76a2828
DM
339 };
340
27916659 341 $filename =~ m|/lxc/(\d+).conf$|
f76a2828
DM
342 || die "got strange filename '$filename'";
343
344 my $vmid = $1;
345
27916659
DM
346 my $conf = $res;
347 my $descr = '';
348 my $section = '';
349
350 my @lines = split(/\n/, $raw);
351 foreach my $line (@lines) {
352 next if $line =~ m/^\s*$/;
353
354 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
355 $section = $1;
356 $conf->{description} = $descr if $descr;
357 $descr = '';
358 $conf = $res->{snapshots}->{$section} = {};
359 next;
a12a36e0 360 }
a12a36e0 361
27916659
DM
362 if ($line =~ m/^\#(.*)\s*$/) {
363 $descr .= PVE::Tools::decode_text($1) . "\n";
364 next;
f76a2828 365 }
5d186e16 366
b43a097e 367 if ($line =~ m/^(lxc\.[a-z0-9\.]+)(:|\s*=)\s*(.*?)\s*$/) {
e576f689
DM
368 my $key = $1;
369 my $value = $3;
370 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
371 push @{$conf->{lxc}}, [$key, $value];
372 } else {
373 warn "vm $vmid - unable to parse config: $line\n";
374 }
375 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
27916659
DM
376 $descr .= PVE::Tools::decode_text($2);
377 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
378 $conf->{snapstate} = $1;
379 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
380 my $key = $1;
5d186e16 381 my $value = $2;
27916659
DM
382 eval { $value = check_type($key, $value); };
383 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
384 $conf->{$key} = $value;
5d186e16 385 } else {
27916659 386 warn "vm $vmid - unable to parse config: $line\n";
5d186e16 387 }
7dfc49cc
DM
388 }
389
27916659 390 $conf->{description} = $descr if $descr;
5d186e16 391
27916659
DM
392 delete $res->{snapstate}; # just to be sure
393
394 return $res;
f76a2828
DM
395}
396
397sub config_list {
398 my $vmlist = PVE::Cluster::get_vmlist();
399 my $res = {};
400 return $res if !$vmlist || !$vmlist->{ids};
401 my $ids = $vmlist->{ids};
402
403 foreach my $vmid (keys %$ids) {
404 next if !$vmid; # skip CT0
405 my $d = $ids->{$vmid};
406 next if !$d->{node} || $d->{node} ne $nodename;
407 next if !$d->{type} || $d->{type} ne 'lxc';
408 $res->{$vmid}->{type} = 'lxc';
409 }
410 return $res;
411}
412
413sub cfs_config_path {
414 my ($vmid, $node) = @_;
415
416 $node = $nodename if !$node;
27916659 417 return "nodes/$node/lxc/$vmid.conf";
f76a2828
DM
418}
419
9c2d4ce9
DM
420sub config_file {
421 my ($vmid, $node) = @_;
422
423 my $cfspath = cfs_config_path($vmid, $node);
424 return "/etc/pve/$cfspath";
425}
426
f76a2828
DM
427sub load_config {
428 my ($vmid) = @_;
429
430 my $cfspath = cfs_config_path($vmid);
431
432 my $conf = PVE::Cluster::cfs_read_file($cfspath);
433 die "container $vmid does not exists\n" if !defined($conf);
434
435 return $conf;
436}
437
5b4657d0
DM
438sub create_config {
439 my ($vmid, $conf) = @_;
440
441 my $dir = "/etc/pve/nodes/$nodename/lxc";
442 mkdir $dir;
443
5b4657d0
DM
444 write_config($vmid, $conf);
445}
446
447sub destroy_config {
448 my ($vmid) = @_;
449
27916659 450 unlink config_file($vmid, $nodename);
5b4657d0
DM
451}
452
f76a2828
DM
453sub write_config {
454 my ($vmid, $conf) = @_;
455
456 my $cfspath = cfs_config_path($vmid);
457
458 PVE::Cluster::cfs_write_file($cfspath, $conf);
459}
460
d14a9a1b
DM
461# flock: we use one file handle per process, so lock file
462# can be called multiple times and succeeds for the same process.
463
464my $lock_handles = {};
465my $lockdir = "/run/lock/lxc";
466
467sub lock_filename {
468 my ($vmid) = @_;
cbb03fea 469
d14a9a1b
DM
470 return "$lockdir/pve-config-{$vmid}.lock";
471}
472
473sub lock_aquire {
474 my ($vmid, $timeout) = @_;
475
476 $timeout = 10 if !$timeout;
477 my $mode = LOCK_EX;
478
479 my $filename = lock_filename($vmid);
480
f99e8278
AD
481 mkdir $lockdir if !-d $lockdir;
482
d14a9a1b
DM
483 my $lock_func = sub {
484 if (!$lock_handles->{$$}->{$filename}) {
485 my $fh = new IO::File(">>$filename") ||
486 die "can't open file - $!\n";
487 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
488 }
489
490 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
491 print STDERR "trying to aquire lock...";
492 my $success;
493 while(1) {
494 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
495 # try again on EINTR (see bug #273)
496 if ($success || ($! != EINTR)) {
497 last;
498 }
499 }
500 if (!$success) {
501 print STDERR " failed\n";
502 die "can't aquire lock - $!\n";
503 }
504
505 $lock_handles->{$$}->{$filename}->{refcount}++;
cbb03fea 506
d14a9a1b
DM
507 print STDERR " OK\n";
508 }
509 };
510
511 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
512 my $err = $@;
513 if ($err) {
514 die "can't lock file '$filename' - $err";
cbb03fea 515 }
d14a9a1b
DM
516}
517
518sub lock_release {
519 my ($vmid) = @_;
520
521 my $filename = lock_filename($vmid);
522
523 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
524 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
525 if ($refcount <= 0) {
526 $lock_handles->{$$}->{$filename} = undef;
527 close ($fh);
528 }
529 }
530}
531
f76a2828
DM
532sub lock_container {
533 my ($vmid, $timeout, $code, @param) = @_;
534
d14a9a1b 535 my $res;
f76a2828 536
d14a9a1b
DM
537 lock_aquire($vmid, $timeout);
538 eval { $res = &$code(@param) };
539 my $err = $@;
540 lock_release($vmid);
f76a2828 541
d14a9a1b 542 die $err if $err;
f76a2828
DM
543
544 return $res;
545}
546
ec52ac21
DM
547sub option_exists {
548 my ($name) = @_;
549
550 return defined($confdesc->{$name});
551}
f76a2828
DM
552
553# add JSON properties for create and set function
554sub json_config_properties {
555 my $prop = shift;
556
557 foreach my $opt (keys %$confdesc) {
09d3ec42 558 next if $opt eq 'parent' || $opt eq 'snaptime';
27916659
DM
559 next if $prop->{$opt};
560 $prop->{$opt} = $confdesc->{$opt};
561 }
562
563 return $prop;
564}
565
566sub json_config_properties_no_rootfs {
567 my $prop = shift;
568
569 foreach my $opt (keys %$confdesc) {
570 next if $prop->{$opt};
09d3ec42 571 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
f76a2828
DM
572 $prop->{$opt} = $confdesc->{$opt};
573 }
574
575 return $prop;
576}
577
822de0c3
DM
578# container status helpers
579
580sub list_active_containers {
cbb03fea 581
822de0c3
DM
582 my $filename = "/proc/net/unix";
583
584 # similar test is used by lcxcontainers.c: list_active_containers
585 my $res = {};
cbb03fea 586
822de0c3
DM
587 my $fh = IO::File->new ($filename, "r");
588 return $res if !$fh;
589
590 while (defined(my $line = <$fh>)) {
591 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
592 my $path = $1;
27916659 593 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
822de0c3
DM
594 $res->{$1} = 1;
595 }
596 }
597 }
598
599 close($fh);
cbb03fea 600
822de0c3
DM
601 return $res;
602}
f76a2828 603
5c752bbf
DM
604# warning: this is slow
605sub check_running {
606 my ($vmid) = @_;
607
608 my $active_hash = list_active_containers();
609
610 return 1 if defined($active_hash->{$vmid});
cbb03fea 611
5c752bbf
DM
612 return undef;
613}
614
10fc3ba5
DM
615sub get_container_disk_usage {
616 my ($vmid) = @_;
617
618 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
cbb03fea 619
10fc3ba5
DM
620 my $res = {
621 total => 0,
622 used => 0,
623 avail => 0,
624 };
625
626 my $parser = sub {
627 my $line = shift;
628 if (my ($fsid, $total, $used, $avail) = $line =~
629 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
630 $res = {
631 total => $total,
632 used => $used,
633 avail => $avail,
634 };
635 }
636 };
637 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
638 warn $@ if $@;
639
640 return $res;
641}
642
f76a2828
DM
643sub vmstatus {
644 my ($opt_vmid) = @_;
645
646 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
647
822de0c3 648 my $active_hash = list_active_containers();
cbb03fea 649
f76a2828 650 foreach my $vmid (keys %$list) {
f76a2828 651 my $d = $list->{$vmid};
10fc3ba5
DM
652
653 my $running = defined($active_hash->{$vmid});
cbb03fea 654
10fc3ba5 655 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
656
657 my $cfspath = cfs_config_path($vmid);
238a56cb 658 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
cbb03fea 659
27916659 660 $d->{name} = $conf->{'hostname'} || "CT$vmid";
238a56cb 661 $d->{name} =~ s/[\s]//g;
cbb03fea 662
27916659 663 $d->{cpus} = $conf->{cpulimit} // 0;
44da0641 664
27916659
DM
665 if ($running) {
666 my $res = get_container_disk_usage($vmid);
667 $d->{disk} = $res->{used};
668 $d->{maxdisk} = $res->{total};
669 } else {
670 $d->{disk} = 0;
671 # use 4GB by default ??
672 if (my $rootfs = $conf->{rootfs}) {
673 my $rootinfo = parse_ct_mountpoint($rootfs);
674 $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
675 } else {
676 $d->{maxdisk} = 4*1024*1024*1024;
10fc3ba5 677 }
238a56cb 678 }
cbb03fea 679
238a56cb
DM
680 $d->{mem} = 0;
681 $d->{swap} = 0;
95df9a12
DM
682 $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
683 $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
e901d418 684
238a56cb
DM
685 $d->{uptime} = 0;
686 $d->{cpu} = 0;
e901d418 687
238a56cb
DM
688 $d->{netout} = 0;
689 $d->{netin} = 0;
f76a2828 690
238a56cb
DM
691 $d->{diskread} = 0;
692 $d->{diskwrite} = 0;
bb1ac2de
DM
693
694 $d->{template} = is_template($conf);
f76a2828 695 }
cbb03fea 696
238a56cb
DM
697 foreach my $vmid (keys %$list) {
698 my $d = $list->{$vmid};
699 next if $d->{status} ne 'running';
f76a2828 700
22a77285
DM
701 $d->{uptime} = 100; # fixme:
702
238a56cb
DM
703 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
704 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
b5289322
AD
705
706 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
1e647c7c 707 my @bytes = split(/\n/, $blkio_bytes);
b5289322 708 foreach my $byte (@bytes) {
1e647c7c
DM
709 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
710 $d->{diskread} = $2 if $key eq 'Read';
711 $d->{diskwrite} = $2 if $key eq 'Write';
712 }
b5289322 713 }
238a56cb 714 }
cbb03fea 715
f76a2828
DM
716 return $list;
717}
718
27916659
DM
719my $parse_size = sub {
720 my ($value) = @_;
721
722 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
723 my ($size, $unit) = ($1, $3);
724 if ($unit) {
725 if ($unit eq 'K') {
726 $size = $size * 1024;
727 } elsif ($unit eq 'M') {
728 $size = $size * 1024 * 1024;
729 } elsif ($unit eq 'G') {
730 $size = $size * 1024 * 1024 * 1024;
731 }
732 }
733 return int($size);
734};
735
736sub parse_ct_mountpoint {
737 my ($data) = @_;
738
739 $data //= '';
740
741 my $res = {};
742
743 foreach my $p (split (/,/, $data)) {
744 next if $p =~ m/^\s*$/;
745
02c9d10c 746 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
27916659
DM
747 my ($k, $v) = ($1, $2);
748 return undef if defined($res->{$k});
dada5f33 749 $res->{$k} = $v;
27916659
DM
750 } else {
751 if (!$res->{volume} && $p !~ m/=/) {
752 $res->{volume} = $p;
753 } else {
754 return undef;
755 }
756 }
757 }
758
759 return undef if !$res->{volume};
760
761 return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
762
763 if ($res->{size}) {
764 return undef if !defined($res->{size} = &$parse_size($res->{size}));
765 }
766
767 return $res;
768}
7dfc49cc 769
dde7b02b 770sub print_ct_mountpoint {
bb1ac2de
DM
771 my ($info) = @_;
772
773 my $opts = '';
774
775 die "missing volume\n" if !$info->{volume};
776
777 foreach my $o ('size', 'backup') {
7092c9f1 778 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
bb1ac2de
DM
779 }
780
781 return "$info->{volume}$opts";
782}
783
7dfc49cc 784sub print_lxc_network {
f76a2828
DM
785 my $net = shift;
786
bedeaaf1 787 die "no network name defined\n" if !$net->{name};
f76a2828 788
bedeaaf1 789 my $res = "name=$net->{name}";
7dfc49cc 790
bedeaaf1 791 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
792 next if !defined($net->{$k});
793 $res .= ",$k=$net->{$k}";
794 }
7dfc49cc 795
f76a2828
DM
796 return $res;
797}
798
7dfc49cc
DM
799sub parse_lxc_network {
800 my ($data) = @_;
801
802 my $res = {};
803
804 return $res if !$data;
805
806 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 807 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
808 $res->{$1} = $2;
809 } else {
810 return undef;
811 }
812 }
813
814 $res->{type} = 'veth';
93cdbbfb 815 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 816
7dfc49cc
DM
817 return $res;
818}
f76a2828 819
238a56cb
DM
820sub read_cgroup_value {
821 my ($group, $vmid, $name, $full) = @_;
822
823 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
824
825 return PVE::Tools::file_get_contents($path) if $full;
826
827 return PVE::Tools::file_read_firstline($path);
828}
829
bf0b8c43
AD
830sub write_cgroup_value {
831 my ($group, $vmid, $name, $value) = @_;
832
833 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
834 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
835
836}
837
52f1d76b
DM
838sub find_lxc_console_pids {
839
840 my $res = {};
841
842 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
843 my ($pid) = @_;
844
845 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
846 return if !$cmdline;
847
848 my @args = split(/\0/, $cmdline);
849
850 # serach for lxc-console -n <vmid>
cbb03fea 851 return if scalar(@args) != 3;
52f1d76b
DM
852 return if $args[1] ne '-n';
853 return if $args[2] !~ m/^\d+$/;
854 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 855
52f1d76b 856 my $vmid = $args[2];
cbb03fea 857
52f1d76b
DM
858 push @{$res->{$vmid}}, $pid;
859 });
860
861 return $res;
862}
863
bedeaaf1
AD
864sub find_lxc_pid {
865 my ($vmid) = @_;
866
867 my $pid = undef;
868 my $parser = sub {
869 my $line = shift;
8b25977f 870 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1
AD
871 };
872 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
873
8b25977f 874 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 875
8b25977f 876 return $pid;
bedeaaf1
AD
877}
878
55fa4e09
DM
879my $ipv4_reverse_mask = [
880 '0.0.0.0',
881 '128.0.0.0',
882 '192.0.0.0',
883 '224.0.0.0',
884 '240.0.0.0',
885 '248.0.0.0',
886 '252.0.0.0',
887 '254.0.0.0',
888 '255.0.0.0',
889 '255.128.0.0',
890 '255.192.0.0',
891 '255.224.0.0',
892 '255.240.0.0',
893 '255.248.0.0',
894 '255.252.0.0',
895 '255.254.0.0',
896 '255.255.0.0',
897 '255.255.128.0',
898 '255.255.192.0',
899 '255.255.224.0',
900 '255.255.240.0',
901 '255.255.248.0',
902 '255.255.252.0',
903 '255.255.254.0',
904 '255.255.255.0',
905 '255.255.255.128',
906 '255.255.255.192',
907 '255.255.255.224',
908 '255.255.255.240',
909 '255.255.255.248',
910 '255.255.255.252',
911 '255.255.255.254',
912 '255.255.255.255',
913];
cbb03fea
DM
914
915# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
916# CIDR networks
917sub parse_ipv4_cidr {
918 my ($cidr, $noerr) = @_;
919
920 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
921 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
922 }
cbb03fea 923
55fa4e09 924 return undef if $noerr;
cbb03fea 925
55fa4e09
DM
926 die "unable to parse ipv4 address/mask\n";
927}
93285df8 928
a12a36e0
WL
929sub check_lock {
930 my ($conf) = @_;
931
27916659 932 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
a12a36e0
WL
933}
934
27916659 935sub update_lxc_config {
c628ffa1 936 my ($storage_cfg, $vmid, $conf) = @_;
b80dd50a 937
bb1ac2de
DM
938 my $dir = "/var/lib/lxc/$vmid";
939
940 if ($conf->{template}) {
941
942 unlink "$dir/config";
943
944 return;
945 }
946
27916659 947 my $raw = '';
b80dd50a 948
27916659
DM
949 die "missing 'arch' - internal error" if !$conf->{arch};
950 $raw .= "lxc.arch = $conf->{arch}\n";
b80dd50a 951
27916659
DM
952 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
953 if ($ostype eq 'debian' || $ostype eq 'ubuntu' || $ostype eq 'centos') {
954 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
955 } else {
956 die "implement me";
957 }
b80dd50a 958
6f035afe 959 if (!has_dev_console($conf)) {
eeaea429
DM
960 $raw .= "lxc.console = none\n";
961 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
962 }
4f958489 963
0d0ca400 964 my $ttycount = get_tty_count($conf);
27916659 965 $raw .= "lxc.tty = $ttycount\n";
cbb03fea 966
27916659
DM
967 my $utsname = $conf->{hostname} || "CT$vmid";
968 $raw .= "lxc.utsname = $utsname\n";
cbb03fea 969
27916659
DM
970 my $memory = $conf->{memory} || 512;
971 my $swap = $conf->{swap} // 0;
972
973 my $lxcmem = int($memory*1024*1024);
974 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
a12a36e0 975
27916659
DM
976 my $lxcswap = int(($memory + $swap)*1024*1024);
977 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
978
979 if (my $cpulimit = $conf->{cpulimit}) {
980 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
981 my $value = int(100000*$cpulimit);
982 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
a12a36e0
WL
983 }
984
27916659
DM
985 my $shares = $conf->{cpuunits} || 1024;
986 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
987
988 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
c628ffa1
DM
989 my $volid = $rootinfo->{volume};
990 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid);
991
453cb062
DM
992 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
993 PVE::Storage::parse_volname($storage_cfg, $volid);
994
995 die "unable to use template as rootfs\n" if $isBase;
996
c628ffa1 997 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
453cb062
DM
998 my $path = PVE::Storage::path($storage_cfg, $volid);
999
1000 if ($format eq 'subvol') {
1001 $raw .= "lxc.rootfs = $path\n";
1002 } elsif ($format eq 'raw') {
1003 if ($scfg->{path}) {
1004 $raw .= "lxc.rootfs = loop:$path\n";
1005 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
1006 $raw .= "lxc.rootfs = $path\n";
1007 } else {
1008 die "unsupported storage type '$scfg->{type}'\n";
1009 }
644f2f8f 1010 } else {
453cb062 1011 die "unsupported image format '$format'\n";
c628ffa1 1012 }
27916659
DM
1013
1014 my $netcount = 0;
1015 foreach my $k (keys %$conf) {
1016 next if $k !~ m/^net(\d+)$/;
1017 my $ind = $1;
a16d94c8 1018 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1019 $netcount++;
1020 $raw .= "lxc.network.type = veth\n";
18862537 1021 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1022 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1023 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1024 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1025 }
1026
e576f689
DM
1027 if (my $lxcconf = $conf->{lxc}) {
1028 foreach my $entry (@$lxcconf) {
1029 my ($k, $v) = @$entry;
1030 $netcount++ if $k eq 'lxc.network.type';
1031 $raw .= "$k = $v\n";
1032 }
1033 }
27916659 1034
e576f689
DM
1035 $raw .= "lxc.network.type = empty\n" if !$netcount;
1036
27916659
DM
1037 File::Path::mkpath("$dir/rootfs");
1038
1039 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1040}
1041
117636e5
DM
1042# verify and cleanup nameserver list (replace \0 with ' ')
1043sub verify_nameserver_list {
1044 my ($nameserver_list) = @_;
1045
1046 my @list = ();
1047 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1048 PVE::JSONSchema::pve_verify_ip($server);
1049 push @list, $server;
1050 }
1051
1052 return join(' ', @list);
1053}
1054
1055sub verify_searchdomain_list {
1056 my ($searchdomain_list) = @_;
1057
1058 my @list = ();
1059 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1060 # todo: should we add checks for valid dns domains?
1061 push @list, $server;
1062 }
1063
1064 return join(' ', @list);
1065}
1066
27916659 1067sub update_pct_config {
93285df8
DM
1068 my ($vmid, $conf, $running, $param, $delete) = @_;
1069
bf0b8c43
AD
1070 my @nohotplug;
1071
cbb03fea
DM
1072 my $rootdir;
1073 if ($running) {
bedeaaf1 1074 my $pid = find_lxc_pid($vmid);
cbb03fea 1075 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1076 }
1077
93285df8
DM
1078 if (defined($delete)) {
1079 foreach my $opt (@$delete) {
27916659 1080 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1081 die "unable to delete required option '$opt'\n";
1082 } elsif ($opt eq 'swap') {
27916659 1083 delete $conf->{$opt};
bf0b8c43 1084 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
27916659
DM
1085 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1086 delete $conf->{$opt};
4f958489
DM
1087 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1088 $opt eq 'tty' || $opt eq 'console') {
27916659 1089 delete $conf->{$opt};
bf0b8c43
AD
1090 push @nohotplug, $opt;
1091 next if $running;
68fba17b 1092 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1093 delete $conf->{$opt};
68fba17b
AD
1094 next if !$running;
1095 my $netid = $1;
18862537 1096 PVE::Network::veth_delete("veth${vmid}i$netid");
93285df8
DM
1097 } else {
1098 die "implement me"
1099 }
bf0b8c43 1100 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8
DM
1101 }
1102 }
1103
be6383d7
WB
1104 # There's no separate swap size to configure, there's memory and "total"
1105 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1106 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1107 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1108 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1109
1110 $wanted_memory //= ($conf->{memory} || 512);
1111 $wanted_swap //= ($conf->{swap} || 0);
1112
1113 my $total = $wanted_memory + $wanted_swap;
1114 if ($running) {
1115 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1116 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1117 }
27916659
DM
1118 $conf->{memory} = $wanted_memory;
1119 $conf->{swap} = $wanted_swap;
1120
1121 PVE::LXC::write_config($vmid, $conf) if $running;
be6383d7
WB
1122 }
1123
93285df8
DM
1124 foreach my $opt (keys %$param) {
1125 my $value = $param->{$opt};
1126 if ($opt eq 'hostname') {
27916659 1127 $conf->{$opt} = $value;
a99b3509 1128 } elsif ($opt eq 'onboot') {
27916659 1129 $conf->{$opt} = $value ? 1 : 0;
a3249355 1130 } elsif ($opt eq 'startup') {
27916659 1131 $conf->{$opt} = $value;
4f958489 1132 } elsif ($opt eq 'tty' || $opt eq 'console') {
e576f689
DM
1133 $conf->{$opt} = $value;
1134 push @nohotplug, $opt;
1135 next if $running;
ffa1d001 1136 } elsif ($opt eq 'nameserver') {
117636e5 1137 my $list = verify_nameserver_list($value);
27916659 1138 $conf->{$opt} = $list;
bf0b8c43
AD
1139 push @nohotplug, $opt;
1140 next if $running;
ffa1d001 1141 } elsif ($opt eq 'searchdomain') {
117636e5 1142 my $list = verify_searchdomain_list($value);
27916659 1143 $conf->{$opt} = $list;
bf0b8c43
AD
1144 push @nohotplug, $opt;
1145 next if $running;
45573f7c 1146 } elsif ($opt eq 'cpulimit') {
27916659
DM
1147 $conf->{$opt} = $value;
1148 push @nohotplug, $opt; # fixme: hotplug
1149 next;
b80dd50a 1150 } elsif ($opt eq 'cpuunits') {
27916659 1151 $conf->{$opt} = $value;
bf0b8c43 1152 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1153 } elsif ($opt eq 'description') {
27916659 1154 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1155 } elsif ($opt =~ m/^net(\d+)$/) {
1156 my $netid = $1;
a16d94c8 1157 my $net = parse_lxc_network($value);
27916659
DM
1158 if (!$running) {
1159 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1160 } else {
bedeaaf1
AD
1161 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1162 }
93285df8 1163 } else {
a92f66c9 1164 die "implement me: $opt";
93285df8 1165 }
bf0b8c43 1166 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8 1167 }
bf0b8c43 1168
5cfa0567
DM
1169 if ($running && scalar(@nohotplug)) {
1170 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1171 }
93285df8 1172}
c325b32f 1173
6f035afe
DM
1174sub has_dev_console {
1175 my ($conf) = @_;
1176
1177 return !(defined($conf->{console}) && !$conf->{console});
1178}
1179
0d0ca400
DM
1180sub get_tty_count {
1181 my ($conf) = @_;
1182
1183 return $conf->{tty} // $confdesc->{tty}->{default};
1184}
1185
c325b32f
DM
1186sub get_primary_ips {
1187 my ($conf) = @_;
1188
1189 # return data from net0
cbb03fea 1190
27916659 1191 return undef if !defined($conf->{net0});
a16d94c8 1192 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1193
1194 my $ipv4 = $net->{ip};
db78a181
WB
1195 if ($ipv4) {
1196 if ($ipv4 =~ /^(dhcp|manual)$/) {
1197 $ipv4 = undef
1198 } else {
1199 $ipv4 =~ s!/\d+$!!;
1200 }
1201 }
65e5eaa3 1202 my $ipv6 = $net->{ip6};
db78a181
WB
1203 if ($ipv6) {
1204 if ($ipv6 =~ /^(dhcp|manual)$/) {
1205 $ipv6 = undef;
1206 } else {
1207 $ipv6 =~ s!/\d+$!!;
1208 }
1209 }
cbb03fea 1210
c325b32f
DM
1211 return ($ipv4, $ipv6);
1212}
148d1cb4 1213
ef241384 1214
27916659 1215sub destroy_lxc_container {
148d1cb4
DM
1216 my ($storage_cfg, $vmid, $conf) = @_;
1217
27916659
DM
1218 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1219 if (defined($rootinfo->{volume})) {
1220 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
1221 PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
148d1cb4 1222 }
27916659
DM
1223 rmdir "/var/lib/lxc/$vmid/rootfs";
1224 unlink "/var/lib/lxc/$vmid/config";
1225 rmdir "/var/lib/lxc/$vmid";
1226 destroy_config($vmid);
1227
1228 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1229 #PVE::Tools::run_command($cmd);
148d1cb4 1230}
68fba17b 1231
ef241384
DM
1232sub vm_stop_cleanup {
1233 my ($storeage_cfg, $vmid, $conf, $keepActive) = @_;
1234
1235 eval {
1236 if (!$keepActive) {
1237 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1238 PVE::Storage::deactivate_volumes($storeage_cfg, [$rootinfo->{volume}]);
1239 }
1240 };
1241 warn $@ if $@; # avoid errors - just warn
1242}
1243
93cdbbfb
AD
1244my $safe_num_ne = sub {
1245 my ($a, $b) = @_;
1246
1247 return 0 if !defined($a) && !defined($b);
1248 return 1 if !defined($a);
1249 return 1 if !defined($b);
1250
1251 return $a != $b;
1252};
1253
1254my $safe_string_ne = sub {
1255 my ($a, $b) = @_;
1256
1257 return 0 if !defined($a) && !defined($b);
1258 return 1 if !defined($a);
1259 return 1 if !defined($b);
1260
1261 return $a ne $b;
1262};
1263
1264sub update_net {
bedeaaf1 1265 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1266
18862537
WB
1267 if ($newnet->{type} ne 'veth') {
1268 # for when there are physical interfaces
1269 die "cannot update interface of type $newnet->{type}";
1270 }
1271
1272 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1273 my $eth = $newnet->{name};
1274
18862537
WB
1275 if (my $oldnetcfg = $conf->{$opt}) {
1276 my $oldnet = parse_lxc_network($oldnetcfg);
1277
1278 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1279 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1280
18862537 1281 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1282 delete $conf->{$opt};
1283 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1284
18862537 1285 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1286
18862537
WB
1287 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1288 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1289 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1290
18862537 1291 if ($oldnet->{bridge}) {
bedeaaf1 1292 PVE::Network::tap_unplug($veth);
18862537
WB
1293 foreach (qw(bridge tag firewall)) {
1294 delete $oldnet->{$_};
1295 }
1296 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1
AD
1297 PVE::LXC::write_config($vmid, $conf);
1298 }
93cdbbfb 1299
18862537
WB
1300 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1301 foreach (qw(bridge tag firewall)) {
1302 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1303 }
1304 $conf->{$opt} = print_lxc_network($oldnet);
bedeaaf1 1305 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1306 }
1307 } else {
18862537 1308 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1309 }
1310
bedeaaf1 1311 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1312}
1313
1314sub hotplug_net {
18862537 1315 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1316
18862537 1317 my $veth = "veth${vmid}i${netid}";
cbb03fea 1318 my $vethpeer = $veth . "p";
93cdbbfb
AD
1319 my $eth = $newnet->{name};
1320
1321 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1322 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1323
cbb03fea 1324 # attach peer in container
93cdbbfb
AD
1325 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1326 PVE::Tools::run_command($cmd);
1327
cbb03fea 1328 # link up peer in container
93cdbbfb
AD
1329 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1330 PVE::Tools::run_command($cmd);
bedeaaf1 1331
18862537
WB
1332 my $done = { type => 'veth' };
1333 foreach (qw(bridge tag firewall hwaddr name)) {
1334 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1335 }
1336 $conf->{$opt} = print_lxc_network($done);
bedeaaf1
AD
1337
1338 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1339}
1340
68a05bb3 1341sub update_ipconfig {
bedeaaf1
AD
1342 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1343
1344 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1345
18862537 1346 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1347 my $deleted = [];
1348 my $added = [];
8d723477
WB
1349 my $nscmd = sub {
1350 my $cmdargs = shift;
1351 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1352 };
8d723477 1353 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1354
84e0c123 1355 my $change_ip_config = sub {
f39002a6
DM
1356 my ($ipversion) = @_;
1357
1358 my $family_opt = "-$ipversion";
1359 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1360 my $gw= "gw$suffix";
1361 my $ip= "ip$suffix";
bedeaaf1 1362
6178b0dd
WB
1363 my $newip = $newnet->{$ip};
1364 my $newgw = $newnet->{$gw};
1365 my $oldip = $optdata->{$ip};
1366
1367 my $change_ip = &$safe_string_ne($oldip, $newip);
1368 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1369
84e0c123 1370 return if !$change_ip && !$change_gw;
68a05bb3 1371
84e0c123 1372 # step 1: add new IP, if this fails we cancel
6178b0dd 1373 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1374 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1375 if (my $err = $@) {
1376 warn $err;
1377 return;
1378 }
bedeaaf1 1379 }
bedeaaf1 1380
84e0c123
WB
1381 # step 2: replace gateway
1382 # If this fails we delete the added IP and cancel.
1383 # If it succeeds we save the config and delete the old IP, ignoring
1384 # errors. The config is then saved.
1385 # Note: 'ip route replace' can add
1386 if ($change_gw) {
6178b0dd 1387 if ($newgw) {
8d723477 1388 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1389 if (my $err = $@) {
1390 warn $err;
1391 # the route was not replaced, the old IP is still available
1392 # rollback (delete new IP) and cancel
1393 if ($change_ip) {
8d723477 1394 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1395 warn $@ if $@; # no need to die here
1396 }
1397 return;
1398 }
1399 } else {
8d723477 1400 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1401 # if the route was not deleted, the guest might have deleted it manually
1402 # warn and continue
1403 warn $@ if $@;
1404 }
2bfd1615 1405 }
2bfd1615 1406
6178b0dd 1407 # from this point on we save the configuration
84e0c123 1408 # step 3: delete old IP ignoring errors
6178b0dd 1409 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1410 # We need to enable promote_secondaries, otherwise our newly added
1411 # address will be removed along with the old one.
1412 my $promote = 0;
1413 eval {
1414 if ($ipversion == 4) {
1415 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1416 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1417 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1418 }
1419 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1420 };
84e0c123 1421 warn $@ if $@; # no need to die here
8d723477
WB
1422
1423 if ($ipversion == 4) {
1424 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1425 }
bedeaaf1
AD
1426 }
1427
84e0c123
WB
1428 foreach my $property ($ip, $gw) {
1429 if ($newnet->{$property}) {
1430 $optdata->{$property} = $newnet->{$property};
1431 } else {
1432 delete $optdata->{$property};
1433 }
bedeaaf1 1434 }
18862537 1435 $conf->{$opt} = print_lxc_network($optdata);
84e0c123
WB
1436 PVE::LXC::write_config($vmid, $conf);
1437 $lxc_setup->setup_network($conf);
1438 };
bedeaaf1 1439
f39002a6
DM
1440 &$change_ip_config(4);
1441 &$change_ip_config(6);
489e960d
WL
1442
1443}
1444
a92f66c9
WL
1445# Internal snapshots
1446
1447# NOTE: Snapshot create/delete involves several non-atomic
1448# action, and can take a long time.
1449# So we try to avoid locking the file and use 'lock' variable
1450# inside the config file instead.
1451
1452my $snapshot_copy_config = sub {
1453 my ($source, $dest) = @_;
1454
1455 foreach my $k (keys %$source) {
1456 next if $k eq 'snapshots';
09d3ec42
DM
1457 next if $k eq 'snapstate';
1458 next if $k eq 'snaptime';
1459 next if $k eq 'vmstate';
1460 next if $k eq 'lock';
a92f66c9 1461 next if $k eq 'digest';
09d3ec42 1462 next if $k eq 'description';
a92f66c9
WL
1463
1464 $dest->{$k} = $source->{$k};
1465 }
1466};
1467
1468my $snapshot_prepare = sub {
1469 my ($vmid, $snapname, $comment) = @_;
1470
1471 my $snap;
1472
1473 my $updatefn = sub {
1474
1475 my $conf = load_config($vmid);
1476
bb1ac2de
DM
1477 die "you can't take a snapshot if it's a template\n"
1478 if is_template($conf);
1479
a92f66c9
WL
1480 check_lock($conf);
1481
09d3ec42 1482 $conf->{lock} = 'snapshot';
a92f66c9
WL
1483
1484 die "snapshot name '$snapname' already used\n"
1485 if defined($conf->{snapshots}->{$snapname});
1486
1487 my $storecfg = PVE::Storage::config();
1488 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1489
1490 $snap = $conf->{snapshots}->{$snapname} = {};
1491
1492 &$snapshot_copy_config($conf, $snap);
1493
09d3ec42
DM
1494 $snap->{'snapstate'} = "prepare";
1495 $snap->{'snaptime'} = time();
1496 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1497 $conf->{snapshots}->{$snapname} = $snap;
1498
1499 PVE::LXC::write_config($vmid, $conf);
1500 };
1501
1502 lock_container($vmid, 10, $updatefn);
1503
1504 return $snap;
1505};
1506
1507my $snapshot_commit = sub {
1508 my ($vmid, $snapname) = @_;
1509
1510 my $updatefn = sub {
1511
1512 my $conf = load_config($vmid);
1513
1514 die "missing snapshot lock\n"
09d3ec42 1515 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1516
27916659 1517 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1518 if !defined($conf->{snapshots}->{$snapname});
1519
1520 die "wrong snapshot state\n"
09d3ec42
DM
1521 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1522 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1523
09d3ec42
DM
1524 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1525 delete $conf->{lock};
1526 $conf->{parent} = $snapname;
a92f66c9
WL
1527
1528 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1529 };
1530
1531 lock_container($vmid, 10 ,$updatefn);
1532};
1533
1534sub has_feature {
1535 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1536
a92f66c9
WL
1537 #Fixme add other drives if necessary.
1538 my $err;
09d3ec42
DM
1539
1540 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1541 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1542
1543 return $err ? 0 : 1;
1544}
1545
489e960d
WL
1546sub snapshot_create {
1547 my ($vmid, $snapname, $comment) = @_;
1548
a92f66c9
WL
1549 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1550
09d3ec42 1551 my $conf = load_config($vmid);
a92f66c9
WL
1552
1553 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1554 my $running = check_running($vmid);
1555 eval {
1556 if ($running) {
1557 PVE::Tools::run_command($cmd);
1558 };
1559
1560 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1561 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1562 my $volid = $rootinfo->{volume};
a92f66c9
WL
1563
1564 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1565 if ($running) {
1566 PVE::Tools::run_command($cmd);
1567 };
489e960d 1568
a92f66c9
WL
1569 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1570 &$snapshot_commit($vmid, $snapname);
1571 };
1572 if(my $err = $@) {
31429832 1573 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1574 die "$err\n";
1575 }
68a05bb3
AD
1576}
1577
57ccb3f8
WL
1578sub snapshot_delete {
1579 my ($vmid, $snapname, $force) = @_;
1580
31429832
WL
1581 my $snap;
1582
1583 my $conf;
1584
1585 my $updatefn = sub {
1586
1587 $conf = load_config($vmid);
1588
bb1ac2de
DM
1589 die "you can't delete a snapshot if vm is a template\n"
1590 if is_template($conf);
1591
31429832
WL
1592 $snap = $conf->{snapshots}->{$snapname};
1593
1594 check_lock($conf);
1595
1596 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1597
09d3ec42 1598 $snap->{snapstate} = 'delete';
31429832
WL
1599
1600 PVE::LXC::write_config($vmid, $conf);
1601 };
1602
1603 lock_container($vmid, 10, $updatefn);
1604
1605 my $storecfg = PVE::Storage::config();
1606
1607 my $del_snap = sub {
1608
1609 check_lock($conf);
1610
09d3ec42
DM
1611 if ($conf->{parent} eq $snapname) {
1612 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1613 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1614 } else {
09d3ec42 1615 delete $conf->{parent};
31429832
WL
1616 }
1617 }
1618
1619 delete $conf->{snapshots}->{$snapname};
1620
1621 PVE::LXC::write_config($vmid, $conf);
1622 };
1623
09d3ec42
DM
1624 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1625 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1626 my $volid = $rootinfo->{volume};
31429832
WL
1627
1628 eval {
1629 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1630 };
1631 my $err = $@;
1632
1633 if(!$err || ($err && $force)) {
1634 lock_container($vmid, 10, $del_snap);
1635 if ($err) {
1636 die "Can't delete snapshot: $vmid $snapname $err\n";
1637 }
1638 }
57ccb3f8
WL
1639}
1640
723157f6
WL
1641sub snapshot_rollback {
1642 my ($vmid, $snapname) = @_;
1643
6860ba0c
WL
1644 my $storecfg = PVE::Storage::config();
1645
1646 my $conf = load_config($vmid);
1647
bb1ac2de
DM
1648 die "you can't rollback if vm is a template\n" if is_template($conf);
1649
6860ba0c
WL
1650 my $snap = $conf->{snapshots}->{$snapname};
1651
1652 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1653
09d3ec42
DM
1654 my $rootfs = $snap->{rootfs};
1655 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1656 my $volid = $rootinfo->{volume};
1657
1658 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1659
1660 my $updatefn = sub {
1661
09d3ec42
DM
1662 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1663 if $snap->{snapstate};
6860ba0c
WL
1664
1665 check_lock($conf);
6860ba0c 1666
b935932a 1667 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1668
1669 die "unable to rollback vm $vmid: vm is running\n"
1670 if check_running($vmid);
1671
09d3ec42 1672 $conf->{lock} = 'rollback';
6860ba0c
WL
1673
1674 my $forcemachine;
1675
1676 # copy snapshot config to current config
1677
1678 my $tmp_conf = $conf;
1679 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1680 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1681 delete $conf->{snaptime};
1682 delete $conf->{snapname};
1683 $conf->{parent} = $snapname;
6860ba0c
WL
1684
1685 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1686 };
1687
1688 my $unlockfn = sub {
09d3ec42 1689 delete $conf->{lock};
6860ba0c
WL
1690 PVE::LXC::write_config($vmid, $conf);
1691 };
1692
1693 lock_container($vmid, 10, $updatefn);
1694
09d3ec42 1695 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1696
1697 lock_container($vmid, 5, $unlockfn);
723157f6 1698}
b935932a 1699
bb1ac2de
DM
1700sub template_create {
1701 my ($vmid, $conf) = @_;
1702
1703 my $storecfg = PVE::Storage::config();
1704
1705 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1706 my $volid = $rootinfo->{volume};
1707
1708 die "Template feature is not available for '$volid'\n"
1709 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1710
1711 PVE::Storage::activate_volumes($storecfg, [$volid]);
1712
1713 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1714 $rootinfo->{volume} = $template_volid;
dde7b02b 1715 $conf->{rootfs} = print_ct_mountpoint($rootinfo);
bb1ac2de
DM
1716
1717 write_config($vmid, $conf);
1718}
1719
1720sub is_template {
1721 my ($conf) = @_;
1722
1723 return 1 if defined $conf->{template} && $conf->{template} == 1;
1724}
1725
ced7fddb
AD
1726sub foreach_mountpoint {
1727 my ($conf, $func) = @_;
1728
1729 foreach my $ms (keys %$conf) {
1730 next if $ms !~ m/^mp(\d+)/ && $ms ne 'rootfs';
1731
1732 my $mountpoint = parse_ct_mountpoint($conf->{$ms});
1733 next if !$mountpoint;
1734
1735 &$func($ms, $mountpoint);
1736 }
1737}
1738
f76a2828 17391;