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