]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
bump version to 0.9-18
[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);
6aca6740 15use PVE::Tools qw($IPV6RE $IPV4RE dir_glob_foreach);
68fba17b 16use PVE::Network;
52389a07 17use PVE::AccessControl;
228a5a1d 18use PVE::ProcFSTools;
f76a2828
DM
19
20use Data::Dumper;
21
27916659
DM
22my $nodename = PVE::INotify::nodename();
23
24cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
f76a2828 25
7dfc49cc
DM
26PVE::JSONSchema::register_format('pve-lxc-network', \&verify_lxc_network);
27sub verify_lxc_network {
28 my ($value, $noerr) = @_;
29
30 return $value if parse_lxc_network($value);
31
32 return undef if $noerr;
33
34 die "unable to parse network setting\n";
35}
36
27916659
DM
37PVE::JSONSchema::register_format('pve-ct-mountpoint', \&verify_ct_mountpoint);
38sub verify_ct_mountpoint {
39 my ($value, $noerr) = @_;
822de0c3 40
27916659 41 return $value if parse_ct_mountpoint($value);
822de0c3 42
27916659 43 return undef if $noerr;
822de0c3 44
27916659 45 die "unable to parse CT mountpoint options\n";
822de0c3
DM
46}
47
27916659
DM
48PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
49 type => 'string', format => 'pve-ct-mountpoint',
50 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+]',
8fbd2935 51 description => "Use volume as container root.",
27916659
DM
52 optional => 1,
53});
54
52389a07
DM
55PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
56 description => "The name of the snapshot.",
57 type => 'string', format => 'pve-configid',
58 maxLength => 40,
59});
60
27916659 61my $confdesc = {
09d3ec42
DM
62 lock => {
63 optional => 1,
64 type => 'string',
65 description => "Lock/unlock the VM.",
66 enum => [qw(migrate backup snapshot rollback)],
67 },
27916659
DM
68 onboot => {
69 optional => 1,
70 type => 'boolean',
71 description => "Specifies whether a VM will be started during system bootup.",
72 default => 0,
117636e5 73 },
27916659 74 startup => get_standard_option('pve-startup-order'),
bb1ac2de
DM
75 template => {
76 optional => 1,
77 type => 'boolean',
78 description => "Enable/disable Template.",
79 default => 0,
80 },
27916659
DM
81 arch => {
82 optional => 1,
83 type => 'string',
84 enum => ['amd64', 'i386'],
85 description => "OS architecture type.",
86 default => 'amd64',
117636e5 87 },
27916659
DM
88 ostype => {
89 optional => 1,
90 type => 'string',
c0821a36 91 enum => ['debian', 'ubuntu', 'centos', 'archlinux'],
27916659 92 description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
a3249355 93 },
4f958489
DM
94 console => {
95 optional => 1,
96 type => 'boolean',
97 description => "Attach a console device (/dev/console) to the container.",
98 default => 1,
99 },
27916659
DM
100 tty => {
101 optional => 1,
102 type => 'integer',
103 description => "Specify the number of tty available to the container",
104 minimum => 0,
105 maximum => 6,
0d0ca400 106 default => 2,
611fe3aa 107 },
27916659
DM
108 cpulimit => {
109 optional => 1,
110 type => 'number',
111 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.",
112 minimum => 0,
113 maximum => 128,
114 default => 0,
115 },
116 cpuunits => {
117 optional => 1,
118 type => 'integer',
119 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.",
120 minimum => 0,
121 maximum => 500000,
81bee809 122 default => 1024,
27916659
DM
123 },
124 memory => {
125 optional => 1,
126 type => 'integer',
127 description => "Amount of RAM for the VM in MB.",
128 minimum => 16,
129 default => 512,
130 },
131 swap => {
132 optional => 1,
133 type => 'integer',
134 description => "Amount of SWAP for the VM in MB.",
135 minimum => 0,
136 default => 512,
137 },
138 hostname => {
139 optional => 1,
140 description => "Set a host name for the container.",
141 type => 'string',
142 maxLength => 255,
143 },
144 description => {
145 optional => 1,
146 type => 'string',
147 description => "Container description. Only used on the configuration web interface.",
148 },
149 searchdomain => {
150 optional => 1,
151 type => 'string',
152 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
153 },
154 nameserver => {
155 optional => 1,
156 type => 'string',
157 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.",
158 },
159 rootfs => get_standard_option('pve-ct-rootfs'),
09d3ec42
DM
160 parent => {
161 optional => 1,
162 type => 'string', format => 'pve-configid',
163 maxLength => 40,
164 description => "Parent snapshot name. This is used internally, and should not be modified.",
165 },
166 snaptime => {
167 optional => 1,
168 description => "Timestamp for snapshots.",
169 type => 'integer',
170 minimum => 0,
171 },
aca816ad
DM
172 cmode => {
173 optional => 1,
174 description => "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
175 type => 'string',
176 enum => ['shell', 'console', 'tty'],
177 default => 'tty',
178 },
7e806596
AG
179 protection => {
180 optional => 1,
181 type => 'boolean',
182 description => "Sets the protection flag of the container. This will prevent the remove operation.",
183 default => 0,
184 },
f76a2828
DM
185};
186
e576f689
DM
187my $valid_lxc_conf_keys = {
188 'lxc.include' => 1,
189 'lxc.arch' => 1,
190 'lxc.utsname' => 1,
191 'lxc.haltsignal' => 1,
192 'lxc.rebootsignal' => 1,
193 'lxc.stopsignal' => 1,
194 'lxc.init_cmd' => 1,
195 'lxc.network.type' => 1,
196 'lxc.network.flags' => 1,
197 'lxc.network.link' => 1,
198 'lxc.network.mtu' => 1,
199 'lxc.network.name' => 1,
200 'lxc.network.hwaddr' => 1,
201 'lxc.network.ipv4' => 1,
202 'lxc.network.ipv4.gateway' => 1,
203 'lxc.network.ipv6' => 1,
204 'lxc.network.ipv6.gateway' => 1,
205 'lxc.network.script.up' => 1,
206 'lxc.network.script.down' => 1,
207 'lxc.pts' => 1,
208 'lxc.console.logfile' => 1,
209 'lxc.console' => 1,
210 'lxc.tty' => 1,
211 'lxc.devttydir' => 1,
212 'lxc.hook.autodev' => 1,
213 'lxc.autodev' => 1,
214 'lxc.kmsg' => 1,
215 'lxc.mount' => 1,
216 'lxc.mount.entry' => 1,
217 'lxc.mount.auto' => 1,
218 'lxc.rootfs' => 1,
219 'lxc.rootfs.mount' => 1,
220 'lxc.rootfs.options' => 1,
221 # lxc.cgroup.*
222 'lxc.cap.drop' => 1,
223 'lxc.cap.keep' => 1,
224 'lxc.aa_profile' => 1,
225 'lxc.aa_allow_incomplete' => 1,
226 'lxc.se_context' => 1,
227 'lxc.seccomp' => 1,
228 'lxc.id_map' => 1,
229 'lxc.hook.pre-start' => 1,
230 'lxc.hook.pre-mount' => 1,
231 'lxc.hook.mount' => 1,
232 'lxc.hook.start' => 1,
233 'lxc.hook.post-stop' => 1,
234 'lxc.hook.clone' => 1,
235 'lxc.hook.destroy' => 1,
236 'lxc.loglevel' => 1,
237 'lxc.logfile' => 1,
238 'lxc.start.auto' => 1,
239 'lxc.start.delay' => 1,
240 'lxc.start.order' => 1,
241 'lxc.group' => 1,
242 'lxc.environment' => 1,
243 'lxc.' => 1,
244 'lxc.' => 1,
245 'lxc.' => 1,
246 'lxc.' => 1,
247};
248
27916659
DM
249my $MAX_LXC_NETWORKS = 10;
250for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
251 $confdesc->{"net$i"} = {
252 optional => 1,
253 type => 'string', format => 'pve-lxc-network',
254 description => "Specifies network interfaces for the container.\n\n".
255 "The string should have the follow format:\n\n".
256 "-net<[0-9]> bridge=<vmbr<Nummber>>[,hwaddr=<MAC>]\n".
257 "[,mtu=<Number>][,name=<String>][,ip=<IPv4Format/CIDR>]\n".
258 ",ip6=<IPv6Format/CIDR>][,gw=<GatwayIPv4>]\n".
259 ",gw6=<GatwayIPv6>][,firewall=<[1|0]>][,tag=<VlanNo>]",
260 };
90bc31f7
DM
261}
262
02c9d10c
AD
263my $MAX_MOUNT_POINTS = 10;
264for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
265 $confdesc->{"mp$i"} = {
266 optional => 1,
267 type => 'string', format => 'pve-ct-mountpoint',
268 typetext => '[volume=]volume,] [,backup=yes|no] [,size=\d+] [,mp=mountpoint]',
566d5f81 269 description => "Use volume as container mount point (experimental feature).",
02c9d10c
AD
270 optional => 1,
271 };
272}
273
27916659
DM
274sub write_pct_config {
275 my ($filename, $conf) = @_;
f76a2828 276
27916659 277 delete $conf->{snapstate}; # just to be sure
f76a2828 278
27916659
DM
279 my $generate_raw_config = sub {
280 my ($conf) = @_;
f76a2828 281
27916659 282 my $raw = '';
cbb03fea 283
27916659
DM
284 # add description as comment to top of file
285 my $descr = $conf->{description} || '';
286 foreach my $cl (split(/\n/, $descr)) {
287 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
a12a36e0 288 }
fff3a342 289
27916659 290 foreach my $key (sort keys %$conf) {
09d3ec42 291 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
e576f689 292 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
27916659 293 $raw .= "$key: $conf->{$key}\n";
a12a36e0 294 }
e576f689
DM
295
296 if (my $lxcconf = $conf->{lxc}) {
297 foreach my $entry (@$lxcconf) {
298 my ($k, $v) = @$entry;
299 $raw .= "$k: $v\n";
300 }
301 }
302
27916659 303 return $raw;
a12a36e0 304 };
160f0941 305
27916659 306 my $raw = &$generate_raw_config($conf);
a12a36e0 307
27916659
DM
308 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
309 $raw .= "\n[$snapname]\n";
310 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
f76a2828
DM
311 }
312
f76a2828
DM
313 return $raw;
314}
315
27916659
DM
316sub check_type {
317 my ($key, $value) = @_;
822de0c3 318
27916659 319 die "unknown setting '$key'\n" if !$confdesc->{$key};
822de0c3 320
27916659
DM
321 my $type = $confdesc->{$key}->{type};
322
323 if (!defined($value)) {
324 die "got undefined value\n";
325 }
326
327 if ($value =~ m/[\n\r]/) {
328 die "property contains a line feed\n";
329 }
822de0c3 330
27916659
DM
331 if ($type eq 'boolean') {
332 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
333 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
334 die "type check ('boolean') failed - got '$value'\n";
335 } elsif ($type eq 'integer') {
336 return int($1) if $value =~ m/^(\d+)$/;
337 die "type check ('integer') failed - got '$value'\n";
338 } elsif ($type eq 'number') {
339 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
340 die "type check ('number') failed - got '$value'\n";
341 } elsif ($type eq 'string') {
342 if (my $fmt = $confdesc->{$key}->{format}) {
343 PVE::JSONSchema::check_format($fmt, $value);
344 return $value;
345 }
cbb03fea 346 return $value;
822de0c3 347 } else {
27916659 348 die "internal error"
822de0c3 349 }
822de0c3
DM
350}
351
27916659 352sub parse_pct_config {
f76a2828
DM
353 my ($filename, $raw) = @_;
354
355 return undef if !defined($raw);
356
27916659 357 my $res = {
f76a2828 358 digest => Digest::SHA::sha1_hex($raw),
27916659 359 snapshots => {},
f76a2828
DM
360 };
361
27916659 362 $filename =~ m|/lxc/(\d+).conf$|
f76a2828
DM
363 || die "got strange filename '$filename'";
364
365 my $vmid = $1;
366
27916659
DM
367 my $conf = $res;
368 my $descr = '';
369 my $section = '';
370
371 my @lines = split(/\n/, $raw);
372 foreach my $line (@lines) {
373 next if $line =~ m/^\s*$/;
374
375 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
376 $section = $1;
377 $conf->{description} = $descr if $descr;
378 $descr = '';
379 $conf = $res->{snapshots}->{$section} = {};
380 next;
a12a36e0 381 }
a12a36e0 382
27916659
DM
383 if ($line =~ m/^\#(.*)\s*$/) {
384 $descr .= PVE::Tools::decode_text($1) . "\n";
385 next;
f76a2828 386 }
5d186e16 387
8c266861 388 if ($line =~ m/^(lxc\.[a-z0-9_\.]+)(:|\s*=)\s*(.*?)\s*$/) {
e576f689
DM
389 my $key = $1;
390 my $value = $3;
391 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
392 push @{$conf->{lxc}}, [$key, $value];
393 } else {
394 warn "vm $vmid - unable to parse config: $line\n";
395 }
396 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
27916659
DM
397 $descr .= PVE::Tools::decode_text($2);
398 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
399 $conf->{snapstate} = $1;
400 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S+)\s*$/) {
401 my $key = $1;
5d186e16 402 my $value = $2;
27916659
DM
403 eval { $value = check_type($key, $value); };
404 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
405 $conf->{$key} = $value;
5d186e16 406 } else {
27916659 407 warn "vm $vmid - unable to parse config: $line\n";
5d186e16 408 }
7dfc49cc
DM
409 }
410
27916659 411 $conf->{description} = $descr if $descr;
5d186e16 412
27916659
DM
413 delete $res->{snapstate}; # just to be sure
414
415 return $res;
f76a2828
DM
416}
417
418sub config_list {
419 my $vmlist = PVE::Cluster::get_vmlist();
420 my $res = {};
421 return $res if !$vmlist || !$vmlist->{ids};
422 my $ids = $vmlist->{ids};
423
424 foreach my $vmid (keys %$ids) {
425 next if !$vmid; # skip CT0
426 my $d = $ids->{$vmid};
427 next if !$d->{node} || $d->{node} ne $nodename;
428 next if !$d->{type} || $d->{type} ne 'lxc';
429 $res->{$vmid}->{type} = 'lxc';
430 }
431 return $res;
432}
433
434sub cfs_config_path {
435 my ($vmid, $node) = @_;
436
437 $node = $nodename if !$node;
27916659 438 return "nodes/$node/lxc/$vmid.conf";
f76a2828
DM
439}
440
9c2d4ce9
DM
441sub config_file {
442 my ($vmid, $node) = @_;
443
444 my $cfspath = cfs_config_path($vmid, $node);
445 return "/etc/pve/$cfspath";
446}
447
f76a2828 448sub load_config {
d18499cf 449 my ($vmid, $node) = @_;
f76a2828 450
d18499cf
TL
451 $node = $nodename if !$node;
452 my $cfspath = cfs_config_path($vmid, $node);
f76a2828
DM
453
454 my $conf = PVE::Cluster::cfs_read_file($cfspath);
455 die "container $vmid does not exists\n" if !defined($conf);
456
457 return $conf;
458}
459
5b4657d0
DM
460sub create_config {
461 my ($vmid, $conf) = @_;
462
463 my $dir = "/etc/pve/nodes/$nodename/lxc";
464 mkdir $dir;
465
5b4657d0
DM
466 write_config($vmid, $conf);
467}
468
469sub destroy_config {
470 my ($vmid) = @_;
471
27916659 472 unlink config_file($vmid, $nodename);
5b4657d0
DM
473}
474
f76a2828
DM
475sub write_config {
476 my ($vmid, $conf) = @_;
477
478 my $cfspath = cfs_config_path($vmid);
479
480 PVE::Cluster::cfs_write_file($cfspath, $conf);
481}
482
d14a9a1b
DM
483# flock: we use one file handle per process, so lock file
484# can be called multiple times and succeeds for the same process.
485
486my $lock_handles = {};
487my $lockdir = "/run/lock/lxc";
488
489sub lock_filename {
490 my ($vmid) = @_;
cbb03fea 491
53396388 492 return "$lockdir/pve-config-${vmid}.lock";
d14a9a1b
DM
493}
494
495sub lock_aquire {
496 my ($vmid, $timeout) = @_;
497
498 $timeout = 10 if !$timeout;
499 my $mode = LOCK_EX;
500
501 my $filename = lock_filename($vmid);
502
f99e8278
AD
503 mkdir $lockdir if !-d $lockdir;
504
d14a9a1b
DM
505 my $lock_func = sub {
506 if (!$lock_handles->{$$}->{$filename}) {
507 my $fh = new IO::File(">>$filename") ||
508 die "can't open file - $!\n";
509 $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
510 }
511
512 if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode |LOCK_NB)) {
513 print STDERR "trying to aquire lock...";
514 my $success;
515 while(1) {
516 $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
517 # try again on EINTR (see bug #273)
518 if ($success || ($! != EINTR)) {
519 last;
520 }
521 }
522 if (!$success) {
523 print STDERR " failed\n";
524 die "can't aquire lock - $!\n";
525 }
526
d14a9a1b
DM
527 print STDERR " OK\n";
528 }
53396388
DM
529
530 $lock_handles->{$$}->{$filename}->{refcount}++;
d14a9a1b
DM
531 };
532
533 eval { PVE::Tools::run_with_timeout($timeout, $lock_func); };
534 my $err = $@;
535 if ($err) {
536 die "can't lock file '$filename' - $err";
cbb03fea 537 }
d14a9a1b
DM
538}
539
540sub lock_release {
541 my ($vmid) = @_;
542
543 my $filename = lock_filename($vmid);
544
545 if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
546 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
547 if ($refcount <= 0) {
548 $lock_handles->{$$}->{$filename} = undef;
549 close ($fh);
550 }
551 }
552}
553
f76a2828
DM
554sub lock_container {
555 my ($vmid, $timeout, $code, @param) = @_;
556
d14a9a1b 557 my $res;
f76a2828 558
d14a9a1b
DM
559 lock_aquire($vmid, $timeout);
560 eval { $res = &$code(@param) };
561 my $err = $@;
562 lock_release($vmid);
f76a2828 563
d14a9a1b 564 die $err if $err;
f76a2828
DM
565
566 return $res;
567}
568
ec52ac21
DM
569sub option_exists {
570 my ($name) = @_;
571
572 return defined($confdesc->{$name});
573}
f76a2828
DM
574
575# add JSON properties for create and set function
576sub json_config_properties {
577 my $prop = shift;
578
579 foreach my $opt (keys %$confdesc) {
09d3ec42 580 next if $opt eq 'parent' || $opt eq 'snaptime';
27916659
DM
581 next if $prop->{$opt};
582 $prop->{$opt} = $confdesc->{$opt};
583 }
584
585 return $prop;
586}
587
588sub json_config_properties_no_rootfs {
589 my $prop = shift;
590
591 foreach my $opt (keys %$confdesc) {
592 next if $prop->{$opt};
09d3ec42 593 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
f76a2828
DM
594 $prop->{$opt} = $confdesc->{$opt};
595 }
596
597 return $prop;
598}
599
822de0c3
DM
600# container status helpers
601
602sub list_active_containers {
cbb03fea 603
822de0c3
DM
604 my $filename = "/proc/net/unix";
605
606 # similar test is used by lcxcontainers.c: list_active_containers
607 my $res = {};
cbb03fea 608
822de0c3
DM
609 my $fh = IO::File->new ($filename, "r");
610 return $res if !$fh;
611
612 while (defined(my $line = <$fh>)) {
613 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
614 my $path = $1;
27916659 615 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
822de0c3
DM
616 $res->{$1} = 1;
617 }
618 }
619 }
620
621 close($fh);
cbb03fea 622
822de0c3
DM
623 return $res;
624}
f76a2828 625
5c752bbf
DM
626# warning: this is slow
627sub check_running {
628 my ($vmid) = @_;
629
630 my $active_hash = list_active_containers();
631
632 return 1 if defined($active_hash->{$vmid});
cbb03fea 633
5c752bbf
DM
634 return undef;
635}
636
10fc3ba5
DM
637sub get_container_disk_usage {
638 my ($vmid) = @_;
639
640 my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df', '-P', '-B', '1', '/'];
cbb03fea 641
10fc3ba5
DM
642 my $res = {
643 total => 0,
644 used => 0,
645 avail => 0,
646 };
647
648 my $parser = sub {
649 my $line = shift;
650 if (my ($fsid, $total, $used, $avail) = $line =~
651 m/^(\S+.*)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/) {
652 $res = {
653 total => $total,
654 used => $used,
655 avail => $avail,
656 };
657 }
658 };
659 eval { PVE::Tools::run_command($cmd, timeout => 1, outfunc => $parser); };
660 warn $@ if $@;
661
662 return $res;
663}
664
f76a2828
DM
665sub vmstatus {
666 my ($opt_vmid) = @_;
667
668 my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
669
822de0c3 670 my $active_hash = list_active_containers();
cbb03fea 671
f76a2828 672 foreach my $vmid (keys %$list) {
f76a2828 673 my $d = $list->{$vmid};
10fc3ba5
DM
674
675 my $running = defined($active_hash->{$vmid});
cbb03fea 676
10fc3ba5 677 $d->{status} = $running ? 'running' : 'stopped';
f76a2828
DM
678
679 my $cfspath = cfs_config_path($vmid);
238a56cb 680 my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
cbb03fea 681
27916659 682 $d->{name} = $conf->{'hostname'} || "CT$vmid";
238a56cb 683 $d->{name} =~ s/[\s]//g;
cbb03fea 684
27916659 685 $d->{cpus} = $conf->{cpulimit} // 0;
44da0641 686
27916659
DM
687 if ($running) {
688 my $res = get_container_disk_usage($vmid);
689 $d->{disk} = $res->{used};
690 $d->{maxdisk} = $res->{total};
691 } else {
692 $d->{disk} = 0;
693 # use 4GB by default ??
694 if (my $rootfs = $conf->{rootfs}) {
695 my $rootinfo = parse_ct_mountpoint($rootfs);
696 $d->{maxdisk} = int(($rootinfo->{size} || 4)*1024*1024)*1024;
697 } else {
698 $d->{maxdisk} = 4*1024*1024*1024;
10fc3ba5 699 }
238a56cb 700 }
cbb03fea 701
238a56cb
DM
702 $d->{mem} = 0;
703 $d->{swap} = 0;
95df9a12
DM
704 $d->{maxmem} = ($conf->{memory}||512)*1024*1024;
705 $d->{maxswap} = ($conf->{swap}//0)*1024*1024;
e901d418 706
238a56cb
DM
707 $d->{uptime} = 0;
708 $d->{cpu} = 0;
e901d418 709
238a56cb
DM
710 $d->{netout} = 0;
711 $d->{netin} = 0;
f76a2828 712
238a56cb
DM
713 $d->{diskread} = 0;
714 $d->{diskwrite} = 0;
bb1ac2de
DM
715
716 $d->{template} = is_template($conf);
f76a2828 717 }
cbb03fea 718
238a56cb
DM
719 foreach my $vmid (keys %$list) {
720 my $d = $list->{$vmid};
721 next if $d->{status} ne 'running';
f76a2828 722
22a77285
DM
723 $d->{uptime} = 100; # fixme:
724
238a56cb
DM
725 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
726 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
b5289322
AD
727
728 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
1e647c7c 729 my @bytes = split(/\n/, $blkio_bytes);
b5289322 730 foreach my $byte (@bytes) {
1e647c7c
DM
731 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
732 $d->{diskread} = $2 if $key eq 'Read';
733 $d->{diskwrite} = $2 if $key eq 'Write';
734 }
b5289322 735 }
238a56cb 736 }
cbb03fea 737
f76a2828
DM
738 return $list;
739}
740
27916659
DM
741my $parse_size = sub {
742 my ($value) = @_;
743
744 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
745 my ($size, $unit) = ($1, $3);
746 if ($unit) {
747 if ($unit eq 'K') {
748 $size = $size * 1024;
749 } elsif ($unit eq 'M') {
750 $size = $size * 1024 * 1024;
751 } elsif ($unit eq 'G') {
752 $size = $size * 1024 * 1024 * 1024;
753 }
754 }
755 return int($size);
756};
757
8ed5ff9d
DM
758my $format_size = sub {
759 my ($size) = @_;
760
761 $size = int($size);
762
763 my $kb = int($size/1024);
764 return $size if $kb*1024 != $size;
765
766 my $mb = int($kb/1024);
767 return "${kb}K" if $mb*1024 != $kb;
768
769 my $gb = int($mb/1024);
770 return "${mb}M" if $gb*1024 != $mb;
771
772 return "${gb}G";
773};
774
27916659
DM
775sub parse_ct_mountpoint {
776 my ($data) = @_;
777
778 $data //= '';
779
780 my $res = {};
781
782 foreach my $p (split (/,/, $data)) {
783 next if $p =~ m/^\s*$/;
784
02c9d10c 785 if ($p =~ m/^(volume|backup|size|mp)=(.+)$/) {
27916659
DM
786 my ($k, $v) = ($1, $2);
787 return undef if defined($res->{$k});
dada5f33 788 $res->{$k} = $v;
27916659
DM
789 } else {
790 if (!$res->{volume} && $p !~ m/=/) {
791 $res->{volume} = $p;
792 } else {
793 return undef;
794 }
795 }
796 }
797
d33329c2 798 return undef if !defined($res->{volume});
27916659
DM
799
800 return undef if $res->{backup} && $res->{backup} !~ m/^(yes|no)$/;
801
802 if ($res->{size}) {
803 return undef if !defined($res->{size} = &$parse_size($res->{size}));
804 }
805
806 return $res;
807}
7dfc49cc 808
dde7b02b 809sub print_ct_mountpoint {
4fee75fd 810 my ($info, $nomp) = @_;
bb1ac2de
DM
811
812 my $opts = '';
813
814 die "missing volume\n" if !$info->{volume};
815
8ed5ff9d 816 foreach my $o (qw(backup)) {
7092c9f1 817 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
bb1ac2de 818 }
8ed5ff9d
DM
819
820 if ($info->{size}) {
821 $opts .= ",size=" . &$format_size($info->{size});
822 }
823
4fee75fd 824 $opts .= ",mp=$info->{mp}" if !$nomp;
bb1ac2de
DM
825
826 return "$info->{volume}$opts";
827}
828
7dfc49cc 829sub print_lxc_network {
f76a2828
DM
830 my $net = shift;
831
bedeaaf1 832 die "no network name defined\n" if !$net->{name};
f76a2828 833
bedeaaf1 834 my $res = "name=$net->{name}";
7dfc49cc 835
bedeaaf1 836 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
837 next if !defined($net->{$k});
838 $res .= ",$k=$net->{$k}";
839 }
7dfc49cc 840
f76a2828
DM
841 return $res;
842}
843
7dfc49cc
DM
844sub parse_lxc_network {
845 my ($data) = @_;
846
847 my $res = {};
848
849 return $res if !$data;
850
851 foreach my $pv (split (/,/, $data)) {
2b1fc2ea 852 if ($pv =~ m/^(bridge|hwaddr|mtu|name|ip|ip6|gw|gw6|firewall|tag)=(\S+)$/) {
7dfc49cc
DM
853 $res->{$1} = $2;
854 } else {
855 return undef;
856 }
857 }
858
859 $res->{type} = 'veth';
93cdbbfb 860 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 861
7dfc49cc
DM
862 return $res;
863}
f76a2828 864
238a56cb
DM
865sub read_cgroup_value {
866 my ($group, $vmid, $name, $full) = @_;
867
868 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
869
870 return PVE::Tools::file_get_contents($path) if $full;
871
872 return PVE::Tools::file_read_firstline($path);
873}
874
bf0b8c43
AD
875sub write_cgroup_value {
876 my ($group, $vmid, $name, $value) = @_;
877
878 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
879 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
880
881}
882
52f1d76b
DM
883sub find_lxc_console_pids {
884
885 my $res = {};
886
887 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
888 my ($pid) = @_;
889
890 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
891 return if !$cmdline;
892
893 my @args = split(/\0/, $cmdline);
894
895 # serach for lxc-console -n <vmid>
cbb03fea 896 return if scalar(@args) != 3;
52f1d76b
DM
897 return if $args[1] ne '-n';
898 return if $args[2] !~ m/^\d+$/;
899 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 900
52f1d76b 901 my $vmid = $args[2];
cbb03fea 902
52f1d76b
DM
903 push @{$res->{$vmid}}, $pid;
904 });
905
906 return $res;
907}
908
bedeaaf1
AD
909sub find_lxc_pid {
910 my ($vmid) = @_;
911
912 my $pid = undef;
913 my $parser = sub {
914 my $line = shift;
8b25977f 915 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1
AD
916 };
917 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
918
8b25977f 919 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 920
8b25977f 921 return $pid;
bedeaaf1
AD
922}
923
55fa4e09
DM
924my $ipv4_reverse_mask = [
925 '0.0.0.0',
926 '128.0.0.0',
927 '192.0.0.0',
928 '224.0.0.0',
929 '240.0.0.0',
930 '248.0.0.0',
931 '252.0.0.0',
932 '254.0.0.0',
933 '255.0.0.0',
934 '255.128.0.0',
935 '255.192.0.0',
936 '255.224.0.0',
937 '255.240.0.0',
938 '255.248.0.0',
939 '255.252.0.0',
940 '255.254.0.0',
941 '255.255.0.0',
942 '255.255.128.0',
943 '255.255.192.0',
944 '255.255.224.0',
945 '255.255.240.0',
946 '255.255.248.0',
947 '255.255.252.0',
948 '255.255.254.0',
949 '255.255.255.0',
950 '255.255.255.128',
951 '255.255.255.192',
952 '255.255.255.224',
953 '255.255.255.240',
954 '255.255.255.248',
955 '255.255.255.252',
956 '255.255.255.254',
957 '255.255.255.255',
958];
cbb03fea
DM
959
960# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
961# CIDR networks
962sub parse_ipv4_cidr {
963 my ($cidr, $noerr) = @_;
964
965 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
966 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
967 }
cbb03fea 968
55fa4e09 969 return undef if $noerr;
cbb03fea 970
55fa4e09
DM
971 die "unable to parse ipv4 address/mask\n";
972}
93285df8 973
a12a36e0
WL
974sub check_lock {
975 my ($conf) = @_;
976
27916659 977 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
a12a36e0
WL
978}
979
27916659 980sub update_lxc_config {
c628ffa1 981 my ($storage_cfg, $vmid, $conf) = @_;
b80dd50a 982
bb1ac2de
DM
983 my $dir = "/var/lib/lxc/$vmid";
984
985 if ($conf->{template}) {
986
987 unlink "$dir/config";
988
989 return;
990 }
991
27916659 992 my $raw = '';
b80dd50a 993
27916659
DM
994 die "missing 'arch' - internal error" if !$conf->{arch};
995 $raw .= "lxc.arch = $conf->{arch}\n";
b80dd50a 996
27916659 997 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
c1d32b55 998 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
27916659
DM
999 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1000 } else {
1001 die "implement me";
1002 }
b80dd50a 1003
6f035afe 1004 if (!has_dev_console($conf)) {
eeaea429
DM
1005 $raw .= "lxc.console = none\n";
1006 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1007 }
4f958489 1008
0d0ca400 1009 my $ttycount = get_tty_count($conf);
27916659 1010 $raw .= "lxc.tty = $ttycount\n";
cbb03fea 1011
27916659
DM
1012 my $utsname = $conf->{hostname} || "CT$vmid";
1013 $raw .= "lxc.utsname = $utsname\n";
cbb03fea 1014
27916659
DM
1015 my $memory = $conf->{memory} || 512;
1016 my $swap = $conf->{swap} // 0;
1017
1018 my $lxcmem = int($memory*1024*1024);
1019 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
a12a36e0 1020
27916659
DM
1021 my $lxcswap = int(($memory + $swap)*1024*1024);
1022 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1023
1024 if (my $cpulimit = $conf->{cpulimit}) {
1025 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1026 my $value = int(100000*$cpulimit);
1027 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
a12a36e0
WL
1028 }
1029
27916659
DM
1030 my $shares = $conf->{cpuunits} || 1024;
1031 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1032
21be9680 1033 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
b15c75fc 1034 $mountpoint->{mp} = '/';
21be9680 1035 my $volid = $mountpoint->{volume};
b15c75fc
DM
1036 my $path = mountpoint_mount_path($mountpoint, $storage_cfg);
1037
21be9680 1038 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
c628ffa1 1039
21be9680 1040 if ($storage) {
a3076d81 1041 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
21be9680
AD
1042 $path = "loop:$path" if $scfg->{path};
1043 }
a3076d81 1044
21be9680 1045 $raw .= "lxc.rootfs = $path\n";
27916659
DM
1046
1047 my $netcount = 0;
1048 foreach my $k (keys %$conf) {
1049 next if $k !~ m/^net(\d+)$/;
1050 my $ind = $1;
a16d94c8 1051 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1052 $netcount++;
1053 $raw .= "lxc.network.type = veth\n";
18862537 1054 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1055 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1056 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1057 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1058 }
1059
e576f689
DM
1060 if (my $lxcconf = $conf->{lxc}) {
1061 foreach my $entry (@$lxcconf) {
1062 my ($k, $v) = @$entry;
1063 $netcount++ if $k eq 'lxc.network.type';
1064 $raw .= "$k = $v\n";
1065 }
1066 }
27916659 1067
e576f689
DM
1068 $raw .= "lxc.network.type = empty\n" if !$netcount;
1069
27916659
DM
1070 File::Path::mkpath("$dir/rootfs");
1071
1072 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1073}
1074
117636e5
DM
1075# verify and cleanup nameserver list (replace \0 with ' ')
1076sub verify_nameserver_list {
1077 my ($nameserver_list) = @_;
1078
1079 my @list = ();
1080 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1081 PVE::JSONSchema::pve_verify_ip($server);
1082 push @list, $server;
1083 }
1084
1085 return join(' ', @list);
1086}
1087
1088sub verify_searchdomain_list {
1089 my ($searchdomain_list) = @_;
1090
1091 my @list = ();
1092 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1093 # todo: should we add checks for valid dns domains?
1094 push @list, $server;
1095 }
1096
1097 return join(' ', @list);
1098}
1099
27916659 1100sub update_pct_config {
93285df8
DM
1101 my ($vmid, $conf, $running, $param, $delete) = @_;
1102
bf0b8c43
AD
1103 my @nohotplug;
1104
4fee75fd
WB
1105 my $new_disks = [];
1106
cbb03fea
DM
1107 my $rootdir;
1108 if ($running) {
bedeaaf1 1109 my $pid = find_lxc_pid($vmid);
cbb03fea 1110 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1111 }
1112
93285df8
DM
1113 if (defined($delete)) {
1114 foreach my $opt (@$delete) {
27916659 1115 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1116 die "unable to delete required option '$opt'\n";
1117 } elsif ($opt eq 'swap') {
27916659 1118 delete $conf->{$opt};
bf0b8c43 1119 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1120 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1121 delete $conf->{$opt};
4f958489 1122 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1123 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
27916659 1124 delete $conf->{$opt};
bf0b8c43
AD
1125 push @nohotplug, $opt;
1126 next if $running;
68fba17b 1127 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1128 delete $conf->{$opt};
68fba17b
AD
1129 next if !$running;
1130 my $netid = $1;
18862537 1131 PVE::Network::veth_delete("veth${vmid}i$netid");
7e806596
AG
1132 } elsif ($opt eq 'protection') {
1133 delete $conf->{$opt};
4fee75fd
WB
1134 } elsif ($opt =~ m/^mp(\d+)$/) {
1135 delete $conf->{$opt};
1136 push @nohotplug, $opt;
1137 next if $running;
1138 } elsif ($opt eq 'rootfs') {
b51a98d4 1139 die "implement me"
93285df8
DM
1140 } else {
1141 die "implement me"
1142 }
706c9791 1143 write_config($vmid, $conf) if $running;
93285df8
DM
1144 }
1145 }
1146
be6383d7
WB
1147 # There's no separate swap size to configure, there's memory and "total"
1148 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1149 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1150 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1151 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1152
1153 $wanted_memory //= ($conf->{memory} || 512);
1154 $wanted_swap //= ($conf->{swap} || 0);
1155
1156 my $total = $wanted_memory + $wanted_swap;
1157 if ($running) {
1158 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1159 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1160 }
27916659
DM
1161 $conf->{memory} = $wanted_memory;
1162 $conf->{swap} = $wanted_swap;
1163
706c9791 1164 write_config($vmid, $conf) if $running;
be6383d7
WB
1165 }
1166
93285df8
DM
1167 foreach my $opt (keys %$param) {
1168 my $value = $param->{$opt};
1169 if ($opt eq 'hostname') {
27916659 1170 $conf->{$opt} = $value;
a99b3509 1171 } elsif ($opt eq 'onboot') {
27916659 1172 $conf->{$opt} = $value ? 1 : 0;
a3249355 1173 } elsif ($opt eq 'startup') {
27916659 1174 $conf->{$opt} = $value;
40603eb3 1175 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
e576f689
DM
1176 $conf->{$opt} = $value;
1177 push @nohotplug, $opt;
1178 next if $running;
ffa1d001 1179 } elsif ($opt eq 'nameserver') {
117636e5 1180 my $list = verify_nameserver_list($value);
27916659 1181 $conf->{$opt} = $list;
bf0b8c43
AD
1182 push @nohotplug, $opt;
1183 next if $running;
ffa1d001 1184 } elsif ($opt eq 'searchdomain') {
117636e5 1185 my $list = verify_searchdomain_list($value);
27916659 1186 $conf->{$opt} = $list;
bf0b8c43
AD
1187 push @nohotplug, $opt;
1188 next if $running;
45573f7c 1189 } elsif ($opt eq 'cpulimit') {
27916659
DM
1190 $conf->{$opt} = $value;
1191 push @nohotplug, $opt; # fixme: hotplug
1192 next;
b80dd50a 1193 } elsif ($opt eq 'cpuunits') {
27916659 1194 $conf->{$opt} = $value;
bf0b8c43 1195 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1196 } elsif ($opt eq 'description') {
27916659 1197 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1198 } elsif ($opt =~ m/^net(\d+)$/) {
1199 my $netid = $1;
a16d94c8 1200 my $net = parse_lxc_network($value);
27916659
DM
1201 if (!$running) {
1202 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1203 } else {
bedeaaf1
AD
1204 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1205 }
7e806596
AG
1206 } elsif ($opt eq 'protection') {
1207 $conf->{$opt} = $value ? 1 : 0;
4fee75fd
WB
1208 } elsif ($opt =~ m/^mp(\d+)$/) {
1209 $conf->{$opt} = $value;
1210 push @$new_disks, $opt;
1211 push @nohotplug, $opt;
1212 next;
1213 } elsif ($opt eq 'rootfs') {
b51a98d4 1214 die "implement me: $opt";
93285df8 1215 } else {
a92f66c9 1216 die "implement me: $opt";
93285df8 1217 }
706c9791 1218 write_config($vmid, $conf) if $running;
93285df8 1219 }
bf0b8c43 1220
5cfa0567
DM
1221 if ($running && scalar(@nohotplug)) {
1222 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1223 }
4fee75fd
WB
1224
1225 if (@$new_disks) {
1226 my $storage_cfg = PVE::Storage::config();
6c871c36 1227 create_disks($storage_cfg, $vmid, $conf, $conf);
4fee75fd
WB
1228 mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
1229 umount_all($vmid, $storage_cfg, $conf, 0);
1230 }
93285df8 1231}
c325b32f 1232
6f035afe
DM
1233sub has_dev_console {
1234 my ($conf) = @_;
1235
1236 return !(defined($conf->{console}) && !$conf->{console});
1237}
1238
0d0ca400
DM
1239sub get_tty_count {
1240 my ($conf) = @_;
1241
1242 return $conf->{tty} // $confdesc->{tty}->{default};
1243}
1244
aca816ad
DM
1245sub get_cmode {
1246 my ($conf) = @_;
1247
1248 return $conf->{cmode} // $confdesc->{cmode}->{default};
1249}
1250
1251sub get_console_command {
1252 my ($vmid, $conf) = @_;
1253
1254 my $cmode = get_cmode($conf);
1255
1256 if ($cmode eq 'console') {
1257 return ['lxc-console', '-n', $vmid, '-t', 0];
1258 } elsif ($cmode eq 'tty') {
1259 return ['lxc-console', '-n', $vmid];
1260 } elsif ($cmode eq 'shell') {
1261 return ['lxc-attach', '--clear-env', '-n', $vmid];
1262 } else {
1263 die "internal error";
1264 }
1265}
1266
c325b32f
DM
1267sub get_primary_ips {
1268 my ($conf) = @_;
1269
1270 # return data from net0
cbb03fea 1271
27916659 1272 return undef if !defined($conf->{net0});
a16d94c8 1273 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1274
1275 my $ipv4 = $net->{ip};
db78a181
WB
1276 if ($ipv4) {
1277 if ($ipv4 =~ /^(dhcp|manual)$/) {
1278 $ipv4 = undef
1279 } else {
1280 $ipv4 =~ s!/\d+$!!;
1281 }
1282 }
65e5eaa3 1283 my $ipv6 = $net->{ip6};
db78a181
WB
1284 if ($ipv6) {
1285 if ($ipv6 =~ /^(dhcp|manual)$/) {
1286 $ipv6 = undef;
1287 } else {
1288 $ipv6 =~ s!/\d+$!!;
1289 }
1290 }
cbb03fea 1291
c325b32f
DM
1292 return ($ipv4, $ipv6);
1293}
148d1cb4 1294
ef241384 1295
27916659 1296sub destroy_lxc_container {
148d1cb4
DM
1297 my ($storage_cfg, $vmid, $conf) = @_;
1298
db8989e1
WB
1299 foreach_mountpoint($conf, sub {
1300 my ($ms, $mountpoint) = @_;
1301 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
1302 PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
1303 });
1304
27916659
DM
1305 rmdir "/var/lib/lxc/$vmid/rootfs";
1306 unlink "/var/lib/lxc/$vmid/config";
1307 rmdir "/var/lib/lxc/$vmid";
1308 destroy_config($vmid);
1309
1310 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1311 #PVE::Tools::run_command($cmd);
148d1cb4 1312}
68fba17b 1313
ef241384 1314sub vm_stop_cleanup {
5fa890f0 1315 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
ef241384
DM
1316
1317 eval {
1318 if (!$keepActive) {
bf9d912c 1319
09aa32fd 1320 my $vollist = get_vm_volumes($conf);
a8b6b8a7 1321 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1322 }
1323 };
1324 warn $@ if $@; # avoid errors - just warn
1325}
1326
93cdbbfb
AD
1327my $safe_num_ne = sub {
1328 my ($a, $b) = @_;
1329
1330 return 0 if !defined($a) && !defined($b);
1331 return 1 if !defined($a);
1332 return 1 if !defined($b);
1333
1334 return $a != $b;
1335};
1336
1337my $safe_string_ne = sub {
1338 my ($a, $b) = @_;
1339
1340 return 0 if !defined($a) && !defined($b);
1341 return 1 if !defined($a);
1342 return 1 if !defined($b);
1343
1344 return $a ne $b;
1345};
1346
1347sub update_net {
bedeaaf1 1348 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1349
18862537
WB
1350 if ($newnet->{type} ne 'veth') {
1351 # for when there are physical interfaces
1352 die "cannot update interface of type $newnet->{type}";
1353 }
1354
1355 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1356 my $eth = $newnet->{name};
1357
18862537
WB
1358 if (my $oldnetcfg = $conf->{$opt}) {
1359 my $oldnet = parse_lxc_network($oldnetcfg);
1360
1361 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1362 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1363
18862537 1364 PVE::Network::veth_delete($veth);
bedeaaf1 1365 delete $conf->{$opt};
706c9791 1366 write_config($vmid, $conf);
93cdbbfb 1367
18862537 1368 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1369
18862537
WB
1370 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1371 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1372 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1373
18862537 1374 if ($oldnet->{bridge}) {
bedeaaf1 1375 PVE::Network::tap_unplug($veth);
18862537
WB
1376 foreach (qw(bridge tag firewall)) {
1377 delete $oldnet->{$_};
1378 }
1379 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1380 write_config($vmid, $conf);
bedeaaf1 1381 }
93cdbbfb 1382
18862537
WB
1383 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1384 foreach (qw(bridge tag firewall)) {
1385 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1386 }
1387 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1388 write_config($vmid, $conf);
93cdbbfb
AD
1389 }
1390 } else {
18862537 1391 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1392 }
1393
bedeaaf1 1394 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1395}
1396
1397sub hotplug_net {
18862537 1398 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1399
18862537 1400 my $veth = "veth${vmid}i${netid}";
cbb03fea 1401 my $vethpeer = $veth . "p";
93cdbbfb
AD
1402 my $eth = $newnet->{name};
1403
1404 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1405 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1406
cbb03fea 1407 # attach peer in container
93cdbbfb
AD
1408 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1409 PVE::Tools::run_command($cmd);
1410
cbb03fea 1411 # link up peer in container
93cdbbfb
AD
1412 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1413 PVE::Tools::run_command($cmd);
bedeaaf1 1414
18862537
WB
1415 my $done = { type => 'veth' };
1416 foreach (qw(bridge tag firewall hwaddr name)) {
1417 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1418 }
1419 $conf->{$opt} = print_lxc_network($done);
bedeaaf1 1420
706c9791 1421 write_config($vmid, $conf);
93cdbbfb
AD
1422}
1423
68a05bb3 1424sub update_ipconfig {
bedeaaf1
AD
1425 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1426
f2104b80 1427 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
bedeaaf1 1428
18862537 1429 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1430 my $deleted = [];
1431 my $added = [];
8d723477
WB
1432 my $nscmd = sub {
1433 my $cmdargs = shift;
1434 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1435 };
8d723477 1436 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1437
84e0c123 1438 my $change_ip_config = sub {
f39002a6
DM
1439 my ($ipversion) = @_;
1440
1441 my $family_opt = "-$ipversion";
1442 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1443 my $gw= "gw$suffix";
1444 my $ip= "ip$suffix";
bedeaaf1 1445
6178b0dd
WB
1446 my $newip = $newnet->{$ip};
1447 my $newgw = $newnet->{$gw};
1448 my $oldip = $optdata->{$ip};
1449
1450 my $change_ip = &$safe_string_ne($oldip, $newip);
1451 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1452
84e0c123 1453 return if !$change_ip && !$change_gw;
68a05bb3 1454
84e0c123 1455 # step 1: add new IP, if this fails we cancel
6178b0dd 1456 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1457 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1458 if (my $err = $@) {
1459 warn $err;
1460 return;
1461 }
bedeaaf1 1462 }
bedeaaf1 1463
84e0c123
WB
1464 # step 2: replace gateway
1465 # If this fails we delete the added IP and cancel.
1466 # If it succeeds we save the config and delete the old IP, ignoring
1467 # errors. The config is then saved.
1468 # Note: 'ip route replace' can add
1469 if ($change_gw) {
6178b0dd 1470 if ($newgw) {
8d723477 1471 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1472 if (my $err = $@) {
1473 warn $err;
1474 # the route was not replaced, the old IP is still available
1475 # rollback (delete new IP) and cancel
1476 if ($change_ip) {
8d723477 1477 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1478 warn $@ if $@; # no need to die here
1479 }
1480 return;
1481 }
1482 } else {
8d723477 1483 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1484 # if the route was not deleted, the guest might have deleted it manually
1485 # warn and continue
1486 warn $@ if $@;
1487 }
2bfd1615 1488 }
2bfd1615 1489
6178b0dd 1490 # from this point on we save the configuration
84e0c123 1491 # step 3: delete old IP ignoring errors
6178b0dd 1492 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1493 # We need to enable promote_secondaries, otherwise our newly added
1494 # address will be removed along with the old one.
1495 my $promote = 0;
1496 eval {
1497 if ($ipversion == 4) {
1498 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1499 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1500 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1501 }
1502 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1503 };
84e0c123 1504 warn $@ if $@; # no need to die here
8d723477
WB
1505
1506 if ($ipversion == 4) {
1507 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1508 }
bedeaaf1
AD
1509 }
1510
84e0c123
WB
1511 foreach my $property ($ip, $gw) {
1512 if ($newnet->{$property}) {
1513 $optdata->{$property} = $newnet->{$property};
1514 } else {
1515 delete $optdata->{$property};
1516 }
bedeaaf1 1517 }
18862537 1518 $conf->{$opt} = print_lxc_network($optdata);
706c9791 1519 write_config($vmid, $conf);
84e0c123
WB
1520 $lxc_setup->setup_network($conf);
1521 };
bedeaaf1 1522
f39002a6
DM
1523 &$change_ip_config(4);
1524 &$change_ip_config(6);
489e960d
WL
1525
1526}
1527
a92f66c9
WL
1528# Internal snapshots
1529
1530# NOTE: Snapshot create/delete involves several non-atomic
1531# action, and can take a long time.
1532# So we try to avoid locking the file and use 'lock' variable
1533# inside the config file instead.
1534
1535my $snapshot_copy_config = sub {
1536 my ($source, $dest) = @_;
1537
1538 foreach my $k (keys %$source) {
1539 next if $k eq 'snapshots';
09d3ec42
DM
1540 next if $k eq 'snapstate';
1541 next if $k eq 'snaptime';
1542 next if $k eq 'vmstate';
1543 next if $k eq 'lock';
a92f66c9 1544 next if $k eq 'digest';
09d3ec42 1545 next if $k eq 'description';
a92f66c9
WL
1546
1547 $dest->{$k} = $source->{$k};
1548 }
1549};
1550
1551my $snapshot_prepare = sub {
1552 my ($vmid, $snapname, $comment) = @_;
1553
1554 my $snap;
1555
1556 my $updatefn = sub {
1557
1558 my $conf = load_config($vmid);
1559
bb1ac2de
DM
1560 die "you can't take a snapshot if it's a template\n"
1561 if is_template($conf);
1562
a92f66c9
WL
1563 check_lock($conf);
1564
09d3ec42 1565 $conf->{lock} = 'snapshot';
a92f66c9
WL
1566
1567 die "snapshot name '$snapname' already used\n"
1568 if defined($conf->{snapshots}->{$snapname});
1569
1570 my $storecfg = PVE::Storage::config();
1571 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1572
1573 $snap = $conf->{snapshots}->{$snapname} = {};
1574
1575 &$snapshot_copy_config($conf, $snap);
1576
09d3ec42
DM
1577 $snap->{'snapstate'} = "prepare";
1578 $snap->{'snaptime'} = time();
1579 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1580 $conf->{snapshots}->{$snapname} = $snap;
1581
706c9791 1582 write_config($vmid, $conf);
a92f66c9
WL
1583 };
1584
1585 lock_container($vmid, 10, $updatefn);
1586
1587 return $snap;
1588};
1589
1590my $snapshot_commit = sub {
1591 my ($vmid, $snapname) = @_;
1592
1593 my $updatefn = sub {
1594
1595 my $conf = load_config($vmid);
1596
1597 die "missing snapshot lock\n"
09d3ec42 1598 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1599
27916659 1600 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1601 if !defined($conf->{snapshots}->{$snapname});
1602
1603 die "wrong snapshot state\n"
09d3ec42
DM
1604 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1605 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1606
09d3ec42
DM
1607 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1608 delete $conf->{lock};
1609 $conf->{parent} = $snapname;
a92f66c9 1610
706c9791 1611 write_config($vmid, $conf);
a92f66c9
WL
1612 };
1613
1614 lock_container($vmid, 10 ,$updatefn);
1615};
1616
1617sub has_feature {
1618 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1619
a92f66c9 1620 my $err;
09d3ec42 1621
8bf50651
DM
1622 foreach_mountpoint($conf, sub {
1623 my ($ms, $mountpoint) = @_;
1624
2c3ed8c4
DM
1625 return if $err; # skip further test
1626
8bf50651
DM
1627 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1628
1629 # TODO: implement support for mountpoints
1630 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1631 if $ms ne 'rootfs';
1632 });
a92f66c9
WL
1633
1634 return $err ? 0 : 1;
1635}
1636
489e960d
WL
1637sub snapshot_create {
1638 my ($vmid, $snapname, $comment) = @_;
1639
a92f66c9
WL
1640 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1641
09d3ec42 1642 my $conf = load_config($vmid);
a92f66c9
WL
1643
1644 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1645 my $running = check_running($vmid);
1646 eval {
1647 if ($running) {
1648 PVE::Tools::run_command($cmd);
1649 };
1650
1651 my $storecfg = PVE::Storage::config();
706c9791 1652 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
09d3ec42 1653 my $volid = $rootinfo->{volume};
a92f66c9
WL
1654
1655 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1656 if ($running) {
1657 PVE::Tools::run_command($cmd);
1658 };
489e960d 1659
a92f66c9
WL
1660 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1661 &$snapshot_commit($vmid, $snapname);
1662 };
1663 if(my $err = $@) {
31429832 1664 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1665 die "$err\n";
1666 }
68a05bb3
AD
1667}
1668
57ccb3f8
WL
1669sub snapshot_delete {
1670 my ($vmid, $snapname, $force) = @_;
1671
31429832
WL
1672 my $snap;
1673
1674 my $conf;
1675
1676 my $updatefn = sub {
1677
1678 $conf = load_config($vmid);
1679
bb1ac2de
DM
1680 die "you can't delete a snapshot if vm is a template\n"
1681 if is_template($conf);
1682
31429832
WL
1683 $snap = $conf->{snapshots}->{$snapname};
1684
1685 check_lock($conf);
1686
1687 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1688
09d3ec42 1689 $snap->{snapstate} = 'delete';
31429832 1690
706c9791 1691 write_config($vmid, $conf);
31429832
WL
1692 };
1693
1694 lock_container($vmid, 10, $updatefn);
1695
1696 my $storecfg = PVE::Storage::config();
1697
1698 my $del_snap = sub {
1699
1700 check_lock($conf);
1701
09d3ec42
DM
1702 if ($conf->{parent} eq $snapname) {
1703 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1704 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1705 } else {
09d3ec42 1706 delete $conf->{parent};
31429832
WL
1707 }
1708 }
1709
1710 delete $conf->{snapshots}->{$snapname};
1711
706c9791 1712 write_config($vmid, $conf);
31429832
WL
1713 };
1714
09d3ec42 1715 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
706c9791 1716 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42 1717 my $volid = $rootinfo->{volume};
31429832
WL
1718
1719 eval {
1720 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1721 };
1722 my $err = $@;
1723
1724 if(!$err || ($err && $force)) {
1725 lock_container($vmid, 10, $del_snap);
1726 if ($err) {
1727 die "Can't delete snapshot: $vmid $snapname $err\n";
1728 }
1729 }
57ccb3f8
WL
1730}
1731
723157f6
WL
1732sub snapshot_rollback {
1733 my ($vmid, $snapname) = @_;
1734
6860ba0c
WL
1735 my $storecfg = PVE::Storage::config();
1736
1737 my $conf = load_config($vmid);
1738
bb1ac2de
DM
1739 die "you can't rollback if vm is a template\n" if is_template($conf);
1740
6860ba0c
WL
1741 my $snap = $conf->{snapshots}->{$snapname};
1742
1743 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1744
09d3ec42 1745 my $rootfs = $snap->{rootfs};
706c9791 1746 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42
DM
1747 my $volid = $rootinfo->{volume};
1748
1749 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1750
1751 my $updatefn = sub {
1752
09d3ec42
DM
1753 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1754 if $snap->{snapstate};
6860ba0c
WL
1755
1756 check_lock($conf);
6860ba0c 1757
b935932a 1758 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1759
1760 die "unable to rollback vm $vmid: vm is running\n"
1761 if check_running($vmid);
1762
09d3ec42 1763 $conf->{lock} = 'rollback';
6860ba0c
WL
1764
1765 my $forcemachine;
1766
1767 # copy snapshot config to current config
1768
1769 my $tmp_conf = $conf;
1770 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1771 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1772 delete $conf->{snaptime};
1773 delete $conf->{snapname};
1774 $conf->{parent} = $snapname;
6860ba0c 1775
706c9791 1776 write_config($vmid, $conf);
6860ba0c
WL
1777 };
1778
1779 my $unlockfn = sub {
09d3ec42 1780 delete $conf->{lock};
706c9791 1781 write_config($vmid, $conf);
6860ba0c
WL
1782 };
1783
1784 lock_container($vmid, 10, $updatefn);
1785
09d3ec42 1786 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1787
1788 lock_container($vmid, 5, $unlockfn);
723157f6 1789}
b935932a 1790
bb1ac2de
DM
1791sub template_create {
1792 my ($vmid, $conf) = @_;
1793
1794 my $storecfg = PVE::Storage::config();
1795
706c9791 1796 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
bb1ac2de
DM
1797 my $volid = $rootinfo->{volume};
1798
1799 die "Template feature is not available for '$volid'\n"
1800 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1801
1802 PVE::Storage::activate_volumes($storecfg, [$volid]);
1803
1804 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1805 $rootinfo->{volume} = $template_volid;
4fee75fd 1806 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
bb1ac2de
DM
1807
1808 write_config($vmid, $conf);
1809}
1810
1811sub is_template {
1812 my ($conf) = @_;
1813
1814 return 1 if defined $conf->{template} && $conf->{template} == 1;
1815}
1816
9622e848
DM
1817sub mountpoint_names {
1818 my ($reverse) = @_;
ced7fddb 1819
9622e848 1820 my @names = ('rootfs');
eaebef36
DM
1821
1822 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
9622e848
DM
1823 push @names, "mp$i";
1824 }
1825
1826 return $reverse ? reverse @names : @names;
1827}
1828
1829sub foreach_mountpoint_full {
1830 my ($conf, $reverse, $func) = @_;
1831
1832 foreach my $key (mountpoint_names($reverse)) {
1833 my $value = $conf->{$key};
1834 next if !defined($value);
1835 my $mountpoint = parse_ct_mountpoint($value);
1836 $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
eaebef36 1837 &$func($key, $mountpoint);
ced7fddb
AD
1838 }
1839}
1840
9622e848
DM
1841sub foreach_mountpoint {
1842 my ($conf, $func) = @_;
1843
1844 foreach_mountpoint_full($conf, 0, $func);
1845}
1846
1847sub foreach_mountpoint_reverse {
1848 my ($conf, $func) = @_;
1849
1850 foreach_mountpoint_full($conf, 1, $func);
1851}
1852
52389a07
DM
1853sub check_ct_modify_config_perm {
1854 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1855
1856 return 1 if $authuser ne 'root@pam';
1857
1858 foreach my $opt (@$key_list) {
1859
1860 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1861 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1862 } elsif ($opt eq 'disk') {
1863 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1864 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1865 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1866 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1867 $opt eq 'searchdomain' || $opt eq 'hostname') {
1868 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1869 } else {
1870 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1871 }
1872 }
1873
1874 return 1;
1875}
1876
9622e848 1877sub umount_all {
da629848 1878 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
9622e848
DM
1879
1880 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1881 my $volid_list = get_vm_volumes($conf);
1882
1883 foreach_mountpoint_reverse($conf, sub {
1884 my ($ms, $mountpoint) = @_;
1885
1886 my $volid = $mountpoint->{volume};
1887 my $mount = $mountpoint->{mp};
1888
1889 return if !$volid || !$mount;
1890
d18f96b4 1891 my $mount_path = "$rootdir/$mount";
f845a93d 1892 $mount_path =~ s!/+!/!g;
9622e848 1893
228a5a1d
WL
1894 return if !PVE::ProcFSTools::is_mounted($mount_path);
1895
9622e848 1896 eval {
d18f96b4 1897 PVE::Tools::run_command(['umount', '-d', $mount_path]);
9622e848
DM
1898 };
1899 if (my $err = $@) {
1900 if ($noerr) {
1901 warn $err;
1902 } else {
1903 die $err;
1904 }
1905 }
1906 });
9622e848
DM
1907}
1908
1909sub mount_all {
4fee75fd 1910 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
9622e848
DM
1911
1912 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1adc7e53 1913 File::Path::make_path($rootdir);
9622e848
DM
1914
1915 my $volid_list = get_vm_volumes($conf);
1916 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
1917
1918 eval {
9622e848
DM
1919 foreach_mountpoint($conf, sub {
1920 my ($ms, $mountpoint) = @_;
1921
1922 my $volid = $mountpoint->{volume};
1923 my $mount = $mountpoint->{mp};
1924
1925 return if !$volid || !$mount;
1926
1927 my $image_path = PVE::Storage::path($storage_cfg, $volid);
1928 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1929 PVE::Storage::parse_volname($storage_cfg, $volid);
1930
1931 die "unable to mount base volume - internal error" if $isBase;
1932
4fee75fd 1933 File::Path::make_path "$rootdir/$mount" if $mkdirs;
da629848 1934 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
9622e848
DM
1935 });
1936 };
1937 if (my $err = $@) {
1938 warn "mounting container failed - $err";
1939 umount_all($vmid, $storage_cfg, $conf, 1);
9622e848
DM
1940 }
1941
da629848 1942 return $rootdir;
9622e848
DM
1943}
1944
1945
b15c75fc 1946sub mountpoint_mount_path {
da629848 1947 my ($mountpoint, $storage_cfg, $snapname) = @_;
b15c75fc 1948
da629848 1949 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
b15c75fc 1950}
cc6b0307 1951
b15c75fc 1952# use $rootdir = undef to just return the corresponding mount path
cc6b0307 1953sub mountpoint_mount {
da629848 1954 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
cc6b0307
AD
1955
1956 my $volid = $mountpoint->{volume};
1957 my $mount = $mountpoint->{mp};
b15c75fc 1958
cc6b0307
AD
1959 return if !$volid || !$mount;
1960
b15c75fc
DM
1961 my $mount_path;
1962
1963 if (defined($rootdir)) {
1964 $rootdir =~ s!/+$!!;
1965 $mount_path = "$rootdir/$mount";
f845a93d 1966 $mount_path =~ s!/+!/!g;
b15c75fc 1967 File::Path::mkpath($mount_path);
116ce06f 1968 }
b15c75fc
DM
1969
1970 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
cc6b0307 1971
b15c75fc 1972 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
cc6b0307 1973
b15c75fc
DM
1974 if ($storage) {
1975
1976 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1977 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
da629848 1978 return $path if !$mount_path;
b15c75fc
DM
1979
1980 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1981 PVE::Storage::parse_volname($storage_cfg, $volid);
1982
1983 if ($format eq 'subvol') {
da629848
WB
1984 if ($snapname) {
1985 if ($scfg->{type} eq 'zfspool') {
1986 my $path_arg = $path;
1987 $path_arg =~ s!^/+!!;
1988 PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
b15c75fc 1989 } else {
da629848
WB
1990 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1991 }
1992 } else {
1993 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
b15c75fc
DM
1994 }
1995 return $path;
b15c75fc 1996 } elsif ($format eq 'raw') {
da629848 1997 my @extra_opts;
b15c75fc 1998 if ($scfg->{path}) {
da629848 1999 push @extra_opts, '-o', 'loop';
b15c75fc
DM
2000 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'rbd') {
2001 # do nothing
2002 } else {
2003 die "unsupported storage type '$scfg->{type}'\n";
2004 }
da629848
WB
2005 if ($isBase || defined($snapname)) {
2006 PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2007 } else {
2008 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
b15c75fc
DM
2009 }
2010 return $path;
2011 } else {
2012 die "unsupported image format '$format'\n";
2013 }
2014 } elsif ($volid =~ m|^/dev/.+|) {
2015 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
2016 return $volid;
2017 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2018 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2019 return $volid;
2020 }
2021
2022 die "unsupported storage";
cc6b0307
AD
2023}
2024
9205e9d0
AD
2025sub get_vm_volumes {
2026 my ($conf, $excludes) = @_;
2027
2028 my $vollist = [];
2029
706c9791 2030 foreach_mountpoint($conf, sub {
9205e9d0
AD
2031 my ($ms, $mountpoint) = @_;
2032
2033 return if $excludes && $ms eq $excludes;
2034
2035 my $volid = $mountpoint->{volume};
2036
2037 return if !$volid || $volid =~ m|^/|;
2038
2039 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2040 return if !$sid;
2041
2042 push @$vollist, $volid;
2043 });
2044
2045 return $vollist;
2046}
2047
6c871c36
DM
2048sub mkfs {
2049 my ($dev) = @_;
2050
2051 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2052}
2053
2054sub format_disk {
2055 my ($storage_cfg, $volid) = @_;
2056
2057 if ($volid =~ m!^/dev/.+!) {
2058 mkfs($volid);
2059 return;
2060 }
2061
2062 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2063
2064 die "cannot format volume '$volid' with no storage\n" if !$storage;
2065
2066 my $path = PVE::Storage::path($storage_cfg, $volid);
2067
2068 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2069 PVE::Storage::parse_volname($storage_cfg, $volid);
2070
2071 die "cannot format volume '$volid' (format == $format)\n"
2072 if $format ne 'raw';
2073
2074 mkfs($path);
2075}
2076
2077sub destroy_disks {
2078 my ($storecfg, $vollist) = @_;
2079
2080 foreach my $volid (@$vollist) {
2081 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2082 warn $@ if $@;
2083 }
2084}
2085
2086sub create_disks {
2087 my ($storecfg, $vmid, $settings, $conf) = @_;
2088
2089 my $vollist = [];
2090
2091 eval {
2092 foreach_mountpoint($settings, sub {
2093 my ($ms, $mountpoint) = @_;
2094
2095 my $volid = $mountpoint->{volume};
2096 my $mp = $mountpoint->{mp};
2097
2098 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2099
2100 return if !$storage;
2101
2102 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
8ed5ff9d 2103 my ($storeid, $size_gb) = ($1, $2);
6c871c36 2104
8ed5ff9d 2105 my $size_kb = int(${size_gb}*1024) * 1024;
6c871c36
DM
2106
2107 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2108 # fixme: use better naming ct-$vmid-disk-X.raw?
2109
2110 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
8ed5ff9d 2111 if ($size_kb > 0) {
6c871c36 2112 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
8ed5ff9d 2113 undef, $size_kb);
6c871c36
DM
2114 format_disk($storecfg, $volid);
2115 } else {
2116 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2117 undef, 0);
2118 }
2119 } elsif ($scfg->{type} eq 'zfspool') {
2120
2121 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
8ed5ff9d 2122 undef, $size_kb);
6c871c36
DM
2123 } elsif ($scfg->{type} eq 'drbd') {
2124
8ed5ff9d 2125 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
6c871c36
DM
2126 format_disk($storecfg, $volid);
2127
2128 } elsif ($scfg->{type} eq 'rbd') {
2129
2130 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
8ed5ff9d 2131 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
6c871c36
DM
2132 format_disk($storecfg, $volid);
2133 } else {
2134 die "unable to create containers on storage type '$scfg->{type}'\n";
2135 }
2136 push @$vollist, $volid;
8ed5ff9d 2137 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
ab4232be 2138 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
6c871c36
DM
2139 } else {
2140 # use specified/existing volid
2141 }
2142 });
2143 };
2144 # free allocated images on error
2145 if (my $err = $@) {
2146 destroy_disks($storecfg, $vollist);
2147 die $err;
2148 }
2149 return $vollist;
2150}
2151
68e8f3c5
DM
2152# bash completion helper
2153
2154sub complete_os_templates {
2155 my ($cmdname, $pname, $cvalue) = @_;
2156
2157 my $cfg = PVE::Storage::config();
2158
9e9bc3a6 2159 my $storeid;
68e8f3c5
DM
2160
2161 if ($cvalue =~ m/^([^:]+):/) {
2162 $storeid = $1;
2163 }
2164
2165 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2166 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2167
2168 my $res = [];
2169 foreach my $id (keys %$data) {
2170 foreach my $item (@{$data->{$id}}) {
2171 push @$res, $item->{volid} if defined($item->{volid});
2172 }
2173 }
2174
2175 return $res;
2176}
2177
68e8f3c5
DM
2178my $complete_ctid_full = sub {
2179 my ($running) = @_;
2180
2181 my $idlist = vmstatus();
2182
2183 my $active_hash = list_active_containers();
2184
2185 my $res = [];
2186
2187 foreach my $id (keys %$idlist) {
2188 my $d = $idlist->{$id};
2189 if (defined($running)) {
2190 next if $d->{template};
2191 next if $running && !$active_hash->{$id};
2192 next if !$running && $active_hash->{$id};
2193 }
2194 push @$res, $id;
2195
2196 }
2197 return $res;
2198};
2199
2200sub complete_ctid {
2201 return &$complete_ctid_full();
2202}
2203
2204sub complete_ctid_stopped {
2205 return &$complete_ctid_full(0);
2206}
2207
2208sub complete_ctid_running {
2209 return &$complete_ctid_full(1);
2210}
2211
f76a2828 22121;