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