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