]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
fix lxc.rootfs path for zfs based containers
[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 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;
873 my $d = PVE::LXC::parse_lxc_network($conf->{$k});
874 $netcount++;
875 $raw .= "lxc.network.type = veth\n";
876 $raw .= "lxc.network.veth.pair = veth$vmid.$ind\n";
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;
943 PVE::Network::veth_delete("veth${vmid}.$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;
1000 my $net = PVE::LXC::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
DM
1022 return undef if !defined($conf->{net0});
1023 my $net = PVE::LXC::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
AD
1084
1085 my $veth = $newnet->{'veth.pair'};
cbb03fea 1086 my $vethpeer = $veth . "p";
93cdbbfb
AD
1087 my $eth = $newnet->{name};
1088
bedeaaf1
AD
1089 if ($conf->{$opt}) {
1090 if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
cbb03fea 1091 &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
93cdbbfb
AD
1092
1093 PVE::Network::veth_delete($veth);
bedeaaf1
AD
1094 delete $conf->{$opt};
1095 PVE::LXC::write_config($vmid, $conf);
93cdbbfb 1096
bedeaaf1
AD
1097 hotplug_net($vmid, $conf, $opt, $newnet);
1098
1099 } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
1100 &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
1101 &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
1102
1103 if ($conf->{$opt}->{bridge}){
1104 PVE::Network::tap_unplug($veth);
1105 delete $conf->{$opt}->{bridge};
1106 delete $conf->{$opt}->{tag};
1107 delete $conf->{$opt}->{firewall};
1108 PVE::LXC::write_config($vmid, $conf);
1109 }
93cdbbfb 1110
93cdbbfb 1111 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
bedeaaf1
AD
1112 $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
1113 $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
1114 $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
1115 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1116 }
1117 } else {
bedeaaf1 1118 hotplug_net($vmid, $conf, $opt, $newnet);
93cdbbfb
AD
1119 }
1120
bedeaaf1 1121 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1122}
1123
1124sub hotplug_net {
bedeaaf1 1125 my ($vmid, $conf, $opt, $newnet) = @_;
93cdbbfb
AD
1126
1127 my $veth = $newnet->{'veth.pair'};
cbb03fea 1128 my $vethpeer = $veth . "p";
93cdbbfb
AD
1129 my $eth = $newnet->{name};
1130
1131 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1132 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1133
cbb03fea 1134 # attach peer in container
93cdbbfb
AD
1135 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1136 PVE::Tools::run_command($cmd);
1137
cbb03fea 1138 # link up peer in container
93cdbbfb
AD
1139 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1140 PVE::Tools::run_command($cmd);
bedeaaf1
AD
1141
1142 $conf->{$opt}->{type} = 'veth';
1143 $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
1144 $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
1145 $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
1146 $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
1147 $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
1148 $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
1149
1150 delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
1151 delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
1152 delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
1153 delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
1154
1155 PVE::LXC::write_config($vmid, $conf);
93cdbbfb
AD
1156}
1157
68a05bb3 1158sub update_ipconfig {
bedeaaf1
AD
1159 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1160
1161 my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
1162
84e0c123
WB
1163 my $optdata = $conf->{$opt};
1164 my $deleted = [];
1165 my $added = [];
8d723477
WB
1166 my $nscmd = sub {
1167 my $cmdargs = shift;
1168 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1169 };
8d723477 1170 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1171
84e0c123 1172 my $change_ip_config = sub {
f39002a6
DM
1173 my ($ipversion) = @_;
1174
1175 my $family_opt = "-$ipversion";
1176 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1177 my $gw= "gw$suffix";
1178 my $ip= "ip$suffix";
bedeaaf1 1179
6178b0dd
WB
1180 my $newip = $newnet->{$ip};
1181 my $newgw = $newnet->{$gw};
1182 my $oldip = $optdata->{$ip};
1183
1184 my $change_ip = &$safe_string_ne($oldip, $newip);
1185 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1186
84e0c123 1187 return if !$change_ip && !$change_gw;
68a05bb3 1188
84e0c123 1189 # step 1: add new IP, if this fails we cancel
6178b0dd 1190 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1191 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1192 if (my $err = $@) {
1193 warn $err;
1194 return;
1195 }
bedeaaf1 1196 }
bedeaaf1 1197
84e0c123
WB
1198 # step 2: replace gateway
1199 # If this fails we delete the added IP and cancel.
1200 # If it succeeds we save the config and delete the old IP, ignoring
1201 # errors. The config is then saved.
1202 # Note: 'ip route replace' can add
1203 if ($change_gw) {
6178b0dd 1204 if ($newgw) {
8d723477 1205 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1206 if (my $err = $@) {
1207 warn $err;
1208 # the route was not replaced, the old IP is still available
1209 # rollback (delete new IP) and cancel
1210 if ($change_ip) {
8d723477 1211 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1212 warn $@ if $@; # no need to die here
1213 }
1214 return;
1215 }
1216 } else {
8d723477 1217 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1218 # if the route was not deleted, the guest might have deleted it manually
1219 # warn and continue
1220 warn $@ if $@;
1221 }
2bfd1615 1222 }
2bfd1615 1223
6178b0dd 1224 # from this point on we save the configuration
84e0c123 1225 # step 3: delete old IP ignoring errors
6178b0dd 1226 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1227 # We need to enable promote_secondaries, otherwise our newly added
1228 # address will be removed along with the old one.
1229 my $promote = 0;
1230 eval {
1231 if ($ipversion == 4) {
1232 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1233 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1234 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1235 }
1236 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1237 };
84e0c123 1238 warn $@ if $@; # no need to die here
8d723477
WB
1239
1240 if ($ipversion == 4) {
1241 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1242 }
bedeaaf1
AD
1243 }
1244
84e0c123
WB
1245 foreach my $property ($ip, $gw) {
1246 if ($newnet->{$property}) {
1247 $optdata->{$property} = $newnet->{$property};
1248 } else {
1249 delete $optdata->{$property};
1250 }
bedeaaf1 1251 }
84e0c123
WB
1252 PVE::LXC::write_config($vmid, $conf);
1253 $lxc_setup->setup_network($conf);
1254 };
bedeaaf1 1255
f39002a6
DM
1256 &$change_ip_config(4);
1257 &$change_ip_config(6);
489e960d
WL
1258
1259}
1260
a92f66c9
WL
1261# Internal snapshots
1262
1263# NOTE: Snapshot create/delete involves several non-atomic
1264# action, and can take a long time.
1265# So we try to avoid locking the file and use 'lock' variable
1266# inside the config file instead.
1267
1268my $snapshot_copy_config = sub {
1269 my ($source, $dest) = @_;
1270
1271 foreach my $k (keys %$source) {
1272 next if $k eq 'snapshots';
09d3ec42
DM
1273 next if $k eq 'snapstate';
1274 next if $k eq 'snaptime';
1275 next if $k eq 'vmstate';
1276 next if $k eq 'lock';
a92f66c9 1277 next if $k eq 'digest';
09d3ec42 1278 next if $k eq 'description';
a92f66c9
WL
1279
1280 $dest->{$k} = $source->{$k};
1281 }
1282};
1283
1284my $snapshot_prepare = sub {
1285 my ($vmid, $snapname, $comment) = @_;
1286
1287 my $snap;
1288
1289 my $updatefn = sub {
1290
1291 my $conf = load_config($vmid);
1292
1293 check_lock($conf);
1294
09d3ec42 1295 $conf->{lock} = 'snapshot';
a92f66c9
WL
1296
1297 die "snapshot name '$snapname' already used\n"
1298 if defined($conf->{snapshots}->{$snapname});
1299
1300 my $storecfg = PVE::Storage::config();
1301 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1302
1303 $snap = $conf->{snapshots}->{$snapname} = {};
1304
1305 &$snapshot_copy_config($conf, $snap);
1306
09d3ec42
DM
1307 $snap->{'snapstate'} = "prepare";
1308 $snap->{'snaptime'} = time();
1309 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1310 $conf->{snapshots}->{$snapname} = $snap;
1311
1312 PVE::LXC::write_config($vmid, $conf);
1313 };
1314
1315 lock_container($vmid, 10, $updatefn);
1316
1317 return $snap;
1318};
1319
1320my $snapshot_commit = sub {
1321 my ($vmid, $snapname) = @_;
1322
1323 my $updatefn = sub {
1324
1325 my $conf = load_config($vmid);
1326
1327 die "missing snapshot lock\n"
09d3ec42 1328 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1329
27916659 1330 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1331 if !defined($conf->{snapshots}->{$snapname});
1332
1333 die "wrong snapshot state\n"
09d3ec42
DM
1334 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1335 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1336
09d3ec42
DM
1337 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1338 delete $conf->{lock};
1339 $conf->{parent} = $snapname;
a92f66c9
WL
1340
1341 PVE::LXC::write_config($vmid, $conf);
a92f66c9
WL
1342 };
1343
1344 lock_container($vmid, 10 ,$updatefn);
1345};
1346
1347sub has_feature {
1348 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1349
a92f66c9
WL
1350 #Fixme add other drives if necessary.
1351 my $err;
09d3ec42
DM
1352
1353 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1354 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $rootinfo->{volume}, $snapname);
a92f66c9
WL
1355
1356 return $err ? 0 : 1;
1357}
1358
489e960d
WL
1359sub snapshot_create {
1360 my ($vmid, $snapname, $comment) = @_;
1361
a92f66c9
WL
1362 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1363
09d3ec42 1364 my $conf = load_config($vmid);
a92f66c9
WL
1365
1366 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1367 my $running = check_running($vmid);
1368 eval {
1369 if ($running) {
1370 PVE::Tools::run_command($cmd);
1371 };
1372
1373 my $storecfg = PVE::Storage::config();
09d3ec42
DM
1374 my $rootinfo = PVE::LXC::parse_ct_mountpoint($conf->{rootfs});
1375 my $volid = $rootinfo->{volume};
a92f66c9
WL
1376
1377 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1378 if ($running) {
1379 PVE::Tools::run_command($cmd);
1380 };
489e960d 1381
a92f66c9
WL
1382 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1383 &$snapshot_commit($vmid, $snapname);
1384 };
1385 if(my $err = $@) {
31429832 1386 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1387 die "$err\n";
1388 }
68a05bb3
AD
1389}
1390
57ccb3f8
WL
1391sub snapshot_delete {
1392 my ($vmid, $snapname, $force) = @_;
1393
31429832
WL
1394 my $snap;
1395
1396 my $conf;
1397
1398 my $updatefn = sub {
1399
1400 $conf = load_config($vmid);
1401
1402 $snap = $conf->{snapshots}->{$snapname};
1403
1404 check_lock($conf);
1405
1406 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1407
09d3ec42 1408 $snap->{snapstate} = 'delete';
31429832
WL
1409
1410 PVE::LXC::write_config($vmid, $conf);
1411 };
1412
1413 lock_container($vmid, 10, $updatefn);
1414
1415 my $storecfg = PVE::Storage::config();
1416
1417 my $del_snap = sub {
1418
1419 check_lock($conf);
1420
09d3ec42
DM
1421 if ($conf->{parent} eq $snapname) {
1422 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1423 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1424 } else {
09d3ec42 1425 delete $conf->{parent};
31429832
WL
1426 }
1427 }
1428
1429 delete $conf->{snapshots}->{$snapname};
1430
1431 PVE::LXC::write_config($vmid, $conf);
1432 };
1433
09d3ec42
DM
1434 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
1435 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1436 my $volid = $rootinfo->{volume};
31429832
WL
1437
1438 eval {
1439 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1440 };
1441 my $err = $@;
1442
1443 if(!$err || ($err && $force)) {
1444 lock_container($vmid, 10, $del_snap);
1445 if ($err) {
1446 die "Can't delete snapshot: $vmid $snapname $err\n";
1447 }
1448 }
57ccb3f8
WL
1449}
1450
723157f6
WL
1451sub snapshot_rollback {
1452 my ($vmid, $snapname) = @_;
1453
6860ba0c
WL
1454 my $storecfg = PVE::Storage::config();
1455
1456 my $conf = load_config($vmid);
1457
1458 my $snap = $conf->{snapshots}->{$snapname};
1459
1460 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1461
09d3ec42
DM
1462 my $rootfs = $snap->{rootfs};
1463 my $rootinfo = PVE::LXC::parse_ct_mountpoint($rootfs);
1464 my $volid = $rootinfo->{volume};
1465
1466 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1467
1468 my $updatefn = sub {
1469
09d3ec42
DM
1470 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1471 if $snap->{snapstate};
6860ba0c
WL
1472
1473 check_lock($conf);
6860ba0c 1474
b935932a 1475 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1476
1477 die "unable to rollback vm $vmid: vm is running\n"
1478 if check_running($vmid);
1479
09d3ec42 1480 $conf->{lock} = 'rollback';
6860ba0c
WL
1481
1482 my $forcemachine;
1483
1484 # copy snapshot config to current config
1485
1486 my $tmp_conf = $conf;
1487 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1488 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1489 delete $conf->{snaptime};
1490 delete $conf->{snapname};
1491 $conf->{parent} = $snapname;
6860ba0c
WL
1492
1493 PVE::LXC::write_config($vmid, $conf);
6860ba0c
WL
1494 };
1495
1496 my $unlockfn = sub {
09d3ec42 1497 delete $conf->{lock};
6860ba0c
WL
1498 PVE::LXC::write_config($vmid, $conf);
1499 };
1500
1501 lock_container($vmid, 10, $updatefn);
1502
09d3ec42 1503 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1504
1505 lock_container($vmid, 5, $unlockfn);
723157f6 1506}
b935932a 1507
f76a2828 15081;