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