]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
bump version to 0.9-8
[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,
102 default => 1000,
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
DM
817sub update_lxc_config {
818 my ($storage_conf, $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});
857 my $rootfs = PVE::Storage::path($storage_conf, $rootinfo->{volume});
858 $raw .= "lxc.rootfs = loop:$rootfs\n"; # fixme
859
860 my $netcount = 0;
861 foreach my $k (keys %$conf) {
862 next if $k !~ m/^net(\d+)$/;
863 my $ind = $1;
864 my $d = PVE::LXC::parse_lxc_network($conf->{$k});
865 $netcount++;
866 $raw .= "lxc.network.type = veth\n";
867 $raw .= "lxc.network.veth.pair = veth$vmid.$ind\n";
868 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
869 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
870 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
871 }
872
27916659
DM
873 $raw .= "lxc.network.type = empty\n" if !$netcount;
874
875 my $dir = "/var/lib/lxc/$vmid";
876 File::Path::mkpath("$dir/rootfs");
877
878 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
879}
880
117636e5
DM
881# verify and cleanup nameserver list (replace \0 with ' ')
882sub verify_nameserver_list {
883 my ($nameserver_list) = @_;
884
885 my @list = ();
886 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
887 PVE::JSONSchema::pve_verify_ip($server);
888 push @list, $server;
889 }
890
891 return join(' ', @list);
892}
893
894sub verify_searchdomain_list {
895 my ($searchdomain_list) = @_;
896
897 my @list = ();
898 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
899 # todo: should we add checks for valid dns domains?
900 push @list, $server;
901 }
902
903 return join(' ', @list);
904}
905
27916659 906sub update_pct_config {
93285df8
DM
907 my ($vmid, $conf, $running, $param, $delete) = @_;
908
bf0b8c43
AD
909 my @nohotplug;
910
cbb03fea
DM
911 my $rootdir;
912 if ($running) {
bedeaaf1 913 my $pid = find_lxc_pid($vmid);
cbb03fea 914 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
915 }
916
93285df8
DM
917 if (defined($delete)) {
918 foreach my $opt (@$delete) {
27916659 919 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
920 die "unable to delete required option '$opt'\n";
921 } elsif ($opt eq 'swap') {
27916659 922 delete $conf->{$opt};
bf0b8c43 923 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
27916659
DM
924 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
925 delete $conf->{$opt};
926 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain') {
927 delete $conf->{$opt};
bf0b8c43
AD
928 push @nohotplug, $opt;
929 next if $running;
68fba17b 930 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 931 delete $conf->{$opt};
68fba17b
AD
932 next if !$running;
933 my $netid = $1;
934 PVE::Network::veth_delete("veth${vmid}.$netid");
93285df8
DM
935 } else {
936 die "implement me"
937 }
bf0b8c43 938 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8
DM
939 }
940 }
941
be6383d7
WB
942 # There's no separate swap size to configure, there's memory and "total"
943 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
944 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
945 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 946 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
947
948 $wanted_memory //= ($conf->{memory} || 512);
949 $wanted_swap //= ($conf->{swap} || 0);
950
951 my $total = $wanted_memory + $wanted_swap;
952 if ($running) {
953 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
954 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 955 }
27916659
DM
956 $conf->{memory} = $wanted_memory;
957 $conf->{swap} = $wanted_swap;
958
959 PVE::LXC::write_config($vmid, $conf) if $running;
be6383d7
WB
960 }
961
93285df8
DM
962 foreach my $opt (keys %$param) {
963 my $value = $param->{$opt};
964 if ($opt eq 'hostname') {
27916659 965 $conf->{$opt} = $value;
a99b3509 966 } elsif ($opt eq 'onboot') {
27916659 967 $conf->{$opt} = $value ? 1 : 0;
a3249355 968 } elsif ($opt eq 'startup') {
27916659 969 $conf->{$opt} = $value;
ffa1d001 970 } elsif ($opt eq 'nameserver') {
117636e5 971 my $list = verify_nameserver_list($value);
27916659 972 $conf->{$opt} = $list;
bf0b8c43
AD
973 push @nohotplug, $opt;
974 next if $running;
ffa1d001 975 } elsif ($opt eq 'searchdomain') {
117636e5 976 my $list = verify_searchdomain_list($value);
27916659 977 $conf->{$opt} = $list;
bf0b8c43
AD
978 push @nohotplug, $opt;
979 next if $running;
45573f7c 980 } elsif ($opt eq 'cpulimit') {
27916659
DM
981 $conf->{$opt} = $value;
982 push @nohotplug, $opt; # fixme: hotplug
983 next;
b80dd50a 984 } elsif ($opt eq 'cpuunits') {
27916659 985 $conf->{$opt} = $value;
bf0b8c43 986 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 987 } elsif ($opt eq 'description') {
27916659 988 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
989 } elsif ($opt =~ m/^net(\d+)$/) {
990 my $netid = $1;
991 my $net = PVE::LXC::parse_lxc_network($value);
27916659
DM
992 if (!$running) {
993 $conf->{$opt} = print_lxc_network($net);
cbb03fea 994 } else {
bedeaaf1
AD
995 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
996 }
93285df8 997 } else {
a92f66c9 998 die "implement me: $opt";
93285df8 999 }
bf0b8c43 1000 PVE::LXC::write_config($vmid, $conf) if $running;
93285df8 1001 }
bf0b8c43 1002
5cfa0567
DM
1003 if ($running && scalar(@nohotplug)) {
1004 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1005 }
93285df8 1006}
c325b32f
DM
1007
1008sub get_primary_ips {
1009 my ($conf) = @_;
1010
1011 # return data from net0
cbb03fea 1012
27916659
DM
1013 return undef if !defined($conf->{net0});
1014 my $net = PVE::LXC::parse_lxc_network($conf->{net0});
c325b32f
DM
1015
1016 my $ipv4 = $net->{ip};
db78a181
WB
1017 if ($ipv4) {
1018 if ($ipv4 =~ /^(dhcp|manual)$/) {
1019 $ipv4 = undef
1020 } else {
1021 $ipv4 =~ s!/\d+$!!;
1022 }
1023 }
65e5eaa3 1024 my $ipv6 = $net->{ip6};
db78a181
WB
1025 if ($ipv6) {
1026 if ($ipv6 =~ /^(dhcp|manual)$/) {
1027 $ipv6 = undef;
1028 } else {
1029 $ipv6 =~ s!/\d+$!!;
1030 }
1031 }
cbb03fea 1032
c325b32f
DM
1033 return ($ipv4, $ipv6);
1034}
148d1cb4 1035
27916659 1036sub destroy_lxc_container {
148d1cb4
DM
1037 my ($storage_cfg, $vmid, $conf) = @_;
1038
27916659
DM
1039 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1040 if (defined($rootinfo->{volume})) {
1041 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $rootinfo->{volume});
1042 PVE::Storage::vdisk_free($storage_cfg, $rootinfo->{volume}) if $vmid == $owner;;
148d1cb4 1043 }
27916659
DM
1044 rmdir "/var/lib/lxc/$vmid/rootfs";
1045 unlink "/var/lib/lxc/$vmid/config";
1046 rmdir "/var/lib/lxc/$vmid";
1047 destroy_config($vmid);
1048
1049 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1050 #PVE::Tools::run_command($cmd);
148d1cb4 1051}
68fba17b 1052
93cdbbfb
AD
1053my $safe_num_ne = sub {
1054 my ($a, $b) = @_;
1055
1056 return 0 if !defined($a) && !defined($b);
1057 return 1 if !defined($a);
1058 return 1 if !defined($b);
1059
1060 return $a != $b;
1061};
1062
1063my $safe_string_ne = sub {
1064 my ($a, $b) = @_;
1065
1066 return 0 if !defined($a) && !defined($b);
1067 return 1 if !defined($a);
1068 return 1 if !defined($b);
1069
1070 return $a ne $b;
1071};
1072
1073sub update_net {
bedeaaf1 1074 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb
AD
1075
1076 my $veth = $newnet->{'veth.pair'};
cbb03fea 1077 my $vethpeer = $veth . "p";
93cdbbfb
AD
1078 my $eth = $newnet->{name};
1079
bedeaaf1
AD
1080 if ($conf->{$opt}) {
1081 if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
cbb03fea 1082 &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
93cdbbfb
AD
1083
1084 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1085 delete $conf->{$opt};
1086 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1087
bedeaaf1
AD
1088 hotplug_net($vmid, $conf, $opt, $newnet);
1089
1090 } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
1091 &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
1092 &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
1093
1094 if ($conf->{$opt}->{bridge}){
1095 PVE::Network::tap_unplug($veth);
1096 delete $conf->{$opt}->{bridge};
1097 delete $conf->{$opt}->{tag};
1098 delete $conf->{$opt}->{firewall};
1099 PVE::LXC::write_config($vmid, $conf);
1100 }
93cdbbfb 1101
93cdbbfb 1102 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
bedeaaf1
AD
1103 $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
1104 $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
1105 $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
1106 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1107 }
1108 } else {
bedeaaf1 1109 hotplug_net($vmid, $conf, $opt, $newnet);
93cdbbfb
AD
1110 }
1111
bedeaaf1 1112 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1113}
1114
1115sub hotplug_net {
bedeaaf1 1116 my ($vmid, $conf, $opt, $newnet) = @_;
93cdbbfb
AD
1117
1118 my $veth = $newnet->{'veth.pair'};
cbb03fea 1119 my $vethpeer = $veth . "p";
93cdbbfb
AD
1120 my $eth = $newnet->{name};
1121
1122 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1123 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1124
cbb03fea 1125 # attach peer in container
93cdbbfb
AD
1126 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1127 PVE::Tools::run_command($cmd);
1128
cbb03fea 1129 # link up peer in container
93cdbbfb
AD
1130 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1131 PVE::Tools::run_command($cmd);
bedeaaf1
AD
1132
1133 $conf->{$opt}->{type} = 'veth';
1134 $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
1135 $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
1136 $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
1137 $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
1138 $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
1139 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1140
1141 delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
1142 delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
1143 delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
1144 delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
1145
1146 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1147}
1148
68a05bb3 1149sub update_ipconfig {
bedeaaf1
AD
1150 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1151
1152 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1153
84e0c123
WB
1154 my $optdata = $conf->{$opt};
1155 my $deleted = [];
1156 my $added = [];
8d723477
WB
1157 my $nscmd = sub {
1158 my $cmdargs = shift;
1159 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1160 };
8d723477 1161 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1162
84e0c123 1163 my $change_ip_config = sub {
f39002a6
DM
1164 my ($ipversion) = @_;
1165
1166 my $family_opt = "-$ipversion";
1167 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1168 my $gw= "gw$suffix";
1169 my $ip= "ip$suffix";
bedeaaf1 1170
6178b0dd
WB
1171 my $newip = $newnet->{$ip};
1172 my $newgw = $newnet->{$gw};
1173 my $oldip = $optdata->{$ip};
1174
1175 my $change_ip = &$safe_string_ne($oldip, $newip);
1176 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1177
84e0c123 1178 return if !$change_ip && !$change_gw;
68a05bb3 1179
84e0c123 1180 # step 1: add new IP, if this fails we cancel
6178b0dd 1181 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1182 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1183 if (my $err = $@) {
1184 warn $err;
1185 return;
1186 }
bedeaaf1 1187 }
bedeaaf1 1188
84e0c123
WB
1189 # step 2: replace gateway
1190 # If this fails we delete the added IP and cancel.
1191 # If it succeeds we save the config and delete the old IP, ignoring
1192 # errors. The config is then saved.
1193 # Note: 'ip route replace' can add
1194 if ($change_gw) {
6178b0dd 1195 if ($newgw) {
8d723477 1196 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1197 if (my $err = $@) {
1198 warn $err;
1199 # the route was not replaced, the old IP is still available
1200 # rollback (delete new IP) and cancel
1201 if ($change_ip) {
8d723477 1202 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1203 warn $@ if $@; # no need to die here
1204 }
1205 return;
1206 }
1207 } else {
8d723477 1208 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1209 # if the route was not deleted, the guest might have deleted it manually
1210 # warn and continue
1211 warn $@ if $@;
1212 }
2bfd1615 1213 }
2bfd1615 1214
6178b0dd 1215 # from this point on we save the configuration
84e0c123 1216 # step 3: delete old IP ignoring errors
6178b0dd 1217 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1218 # We need to enable promote_secondaries, otherwise our newly added
1219 # address will be removed along with the old one.
1220 my $promote = 0;
1221 eval {
1222 if ($ipversion == 4) {
1223 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1224 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1225 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1226 }
1227 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1228 };
84e0c123 1229 warn $@ if $@; # no need to die here
8d723477
WB
1230
1231 if ($ipversion == 4) {
1232 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1233 }
bedeaaf1
AD
1234 }
1235
84e0c123
WB
1236 foreach my $property ($ip, $gw) {
1237 if ($newnet->{$property}) {
1238 $optdata->{$property} = $newnet->{$property};
1239 } else {
1240 delete $optdata->{$property};
1241 }
bedeaaf1 1242 }
84e0c123
WB
1243 PVE::LXC::write_config($vmid, $conf);
1244 $lxc_setup->setup_network($conf);
1245 };
bedeaaf1 1246
f39002a6
DM
1247 &$change_ip_config(4);
1248 &$change_ip_config(6);
489e960d
WL
1249
1250}
1251
a92f66c9
WL
1252# Internal snapshots
1253
1254# NOTE: Snapshot create/delete involves several non-atomic
1255# action, and can take a long time.
1256# So we try to avoid locking the file and use 'lock' variable
1257# inside the config file instead.
1258
1259my $snapshot_copy_config = sub {
1260 my ($source, $dest) = @_;
1261
1262 foreach my $k (keys %$source) {
1263 next if $k eq 'snapshots';
09d3ec42
DM
1264 next if $k eq 'snapstate';
1265 next if $k eq 'snaptime';
1266 next if $k eq 'vmstate';
1267 next if $k eq 'lock';
a92f66c9 1268 next if $k eq 'digest';
09d3ec42 1269 next if $k eq 'description';
a92f66c9
WL
1270
1271 $dest->{$k} = $source->{$k};
1272 }
1273};
1274
1275my $snapshot_prepare = sub {
1276 my ($vmid, $snapname, $comment) = @_;
1277
1278 my $snap;
1279
1280 my $updatefn = sub {
1281
1282 my $conf = load_config($vmid);
1283
1284 check_lock($conf);
1285
09d3ec42 1286 $conf->{lock} = 'snapshot';
a92f66c9
WL
1287
1288 die "snapshot name '$snapname' already used\n"
1289 if defined($conf->{snapshots}->{$snapname});
1290
1291 my $storecfg = PVE::Storage::config();
1292 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1293
1294 $snap = $conf->{snapshots}->{$snapname} = {};
1295
1296 &$snapshot_copy_config($conf, $snap);
1297
09d3ec42
DM
1298 $snap->{'snapstate'} = "prepare";
1299 $snap->{'snaptime'} = time();
1300 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1301 $conf->{snapshots}->{$snapname} = $snap;
1302
1303 PVE::LXC::write_config($vmid, $conf);
1304 };
1305
1306 lock_container($vmid, 10, $updatefn);
1307
1308 return $snap;
1309};
1310
1311my $snapshot_commit = sub {
1312 my ($vmid, $snapname) = @_;
1313
1314 my $updatefn = sub {
1315
1316 my $conf = load_config($vmid);
1317
1318 die "missing snapshot lock\n"
09d3ec42 1319 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1320
27916659 1321 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1322 if !defined($conf->{snapshots}->{$snapname});
1323
1324 die "wrong snapshot state\n"
09d3ec42
DM
1325 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1326 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1327
09d3ec42
DM
1328 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1329 delete $conf->{lock};
1330 $conf->{parent} = $snapname;
a92f66c9
WL
1331
1332 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1333 };
1334
1335 lock_container($vmid, 10 ,$updatefn);
1336};
1337
1338sub has_feature {
1339 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1340
a92f66c9
WL
1341 #Fixme add other drives if necessary.
1342 my $err;
09d3ec42
DM
1343
1344 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1345 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1346
1347 return $err ? 0 : 1;
1348}
1349
489e960d
WL
1350sub snapshot_create {
1351 my ($vmid, $snapname, $comment) = @_;
1352
a92f66c9
WL
1353 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1354
09d3ec42 1355 my $conf = load_config($vmid);
a92f66c9
WL
1356
1357 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1358 my $running = check_running($vmid);
1359 eval {
1360 if ($running) {
1361 PVE::Tools::run_command($cmd);
1362 };
1363
1364 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1365 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1366 my $volid = $rootinfo->{volume};
a92f66c9
WL
1367
1368 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1369 if ($running) {
1370 PVE::Tools::run_command($cmd);
1371 };
489e960d 1372
a92f66c9
WL
1373 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1374 &$snapshot_commit($vmid, $snapname);
1375 };
1376 if(my $err = $@) {
31429832 1377 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1378 die "$err\n";
1379 }
68a05bb3
AD
1380}
1381
57ccb3f8
WL
1382sub snapshot_delete {
1383 my ($vmid, $snapname, $force) = @_;
1384
31429832
WL
1385 my $snap;
1386
1387 my $conf;
1388
1389 my $updatefn = sub {
1390
1391 $conf = load_config($vmid);
1392
1393 $snap = $conf->{snapshots}->{$snapname};
1394
1395 check_lock($conf);
1396
1397 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1398
09d3ec42 1399 $snap->{snapstate} = 'delete';
31429832
WL
1400
1401 PVE::LXC::write_config($vmid, $conf);
1402 };
1403
1404 lock_container($vmid, 10, $updatefn);
1405
1406 my $storecfg = PVE::Storage::config();
1407
1408 my $del_snap = sub {
1409
1410 check_lock($conf);
1411
09d3ec42
DM
1412 if ($conf->{parent} eq $snapname) {
1413 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1414 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1415 } else {
09d3ec42 1416 delete $conf->{parent};
31429832
WL
1417 }
1418 }
1419
1420 delete $conf->{snapshots}->{$snapname};
1421
1422 PVE::LXC::write_config($vmid, $conf);
1423 };
1424
09d3ec42
DM
1425 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1426 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1427 my $volid = $rootinfo->{volume};
31429832
WL
1428
1429 eval {
1430 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1431 };
1432 my $err = $@;
1433
1434 if(!$err || ($err && $force)) {
1435 lock_container($vmid, 10, $del_snap);
1436 if ($err) {
1437 die "Can't delete snapshot: $vmid $snapname $err\n";
1438 }
1439 }
57ccb3f8
WL
1440}
1441
723157f6
WL
1442sub snapshot_rollback {
1443 my ($vmid, $snapname) = @_;
1444
6860ba0c
WL
1445 my $storecfg = PVE::Storage::config();
1446
1447 my $conf = load_config($vmid);
1448
1449 my $snap = $conf->{snapshots}->{$snapname};
1450
1451 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1452
09d3ec42
DM
1453 my $rootfs = $snap->{rootfs};
1454 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1455 my $volid = $rootinfo->{volume};
1456
1457 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1458
1459 my $updatefn = sub {
1460
09d3ec42
DM
1461 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1462 if $snap->{snapstate};
6860ba0c
WL
1463
1464 check_lock($conf);
6860ba0c 1465
b935932a 1466 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1467
1468 die "unable to rollback vm $vmid: vm is running\n"
1469 if check_running($vmid);
1470
09d3ec42 1471 $conf->{lock} = 'rollback';
6860ba0c
WL
1472
1473 my $forcemachine;
1474
1475 # copy snapshot config to current config
1476
1477 my $tmp_conf = $conf;
1478 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1479 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1480 delete $conf->{snaptime};
1481 delete $conf->{snapname};
1482 $conf->{parent} = $snapname;
6860ba0c
WL
1483
1484 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1485 };
1486
1487 my $unlockfn = sub {
09d3ec42 1488 delete $conf->{lock};
6860ba0c
WL
1489 PVE::LXC::write_config($vmid, $conf);
1490 };
1491
1492 lock_container($vmid, 10, $updatefn);
1493
09d3ec42 1494 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1495
1496 lock_container($vmid, 5, $unlockfn);
723157f6 1497}
b935932a 1498
f76a2828 14991;