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