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