]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
add support for LVM
[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} = '/';
b15c75fc 1035
30de33be
DM
1036 my ($path, $use_loopdev) = mountpoint_mount_path($mountpoint, $storage_cfg);
1037 $path = "loop:$path" if $use_loopdev;
a3076d81 1038
21be9680 1039 $raw .= "lxc.rootfs = $path\n";
27916659
DM
1040
1041 my $netcount = 0;
1042 foreach my $k (keys %$conf) {
1043 next if $k !~ m/^net(\d+)$/;
1044 my $ind = $1;
a16d94c8 1045 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1046 $netcount++;
1047 $raw .= "lxc.network.type = veth\n";
18862537 1048 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1049 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1050 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1051 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1052 }
1053
e576f689
DM
1054 if (my $lxcconf = $conf->{lxc}) {
1055 foreach my $entry (@$lxcconf) {
1056 my ($k, $v) = @$entry;
1057 $netcount++ if $k eq 'lxc.network.type';
1058 $raw .= "$k = $v\n";
1059 }
1060 }
27916659 1061
e576f689
DM
1062 $raw .= "lxc.network.type = empty\n" if !$netcount;
1063
27916659
DM
1064 File::Path::mkpath("$dir/rootfs");
1065
1066 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1067}
1068
117636e5
DM
1069# verify and cleanup nameserver list (replace \0 with ' ')
1070sub verify_nameserver_list {
1071 my ($nameserver_list) = @_;
1072
1073 my @list = ();
1074 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1075 PVE::JSONSchema::pve_verify_ip($server);
1076 push @list, $server;
1077 }
1078
1079 return join(' ', @list);
1080}
1081
1082sub verify_searchdomain_list {
1083 my ($searchdomain_list) = @_;
1084
1085 my @list = ();
1086 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1087 # todo: should we add checks for valid dns domains?
1088 push @list, $server;
1089 }
1090
1091 return join(' ', @list);
1092}
1093
27916659 1094sub update_pct_config {
93285df8
DM
1095 my ($vmid, $conf, $running, $param, $delete) = @_;
1096
bf0b8c43
AD
1097 my @nohotplug;
1098
4fee75fd
WB
1099 my $new_disks = [];
1100
cbb03fea
DM
1101 my $rootdir;
1102 if ($running) {
bedeaaf1 1103 my $pid = find_lxc_pid($vmid);
cbb03fea 1104 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1105 }
1106
93285df8
DM
1107 if (defined($delete)) {
1108 foreach my $opt (@$delete) {
27916659 1109 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1110 die "unable to delete required option '$opt'\n";
1111 } elsif ($opt eq 'swap') {
27916659 1112 delete $conf->{$opt};
bf0b8c43 1113 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1114 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1115 delete $conf->{$opt};
4f958489 1116 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1117 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
27916659 1118 delete $conf->{$opt};
bf0b8c43
AD
1119 push @nohotplug, $opt;
1120 next if $running;
68fba17b 1121 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1122 delete $conf->{$opt};
68fba17b
AD
1123 next if !$running;
1124 my $netid = $1;
18862537 1125 PVE::Network::veth_delete("veth${vmid}i$netid");
7e806596
AG
1126 } elsif ($opt eq 'protection') {
1127 delete $conf->{$opt};
4fee75fd
WB
1128 } elsif ($opt =~ m/^mp(\d+)$/) {
1129 delete $conf->{$opt};
1130 push @nohotplug, $opt;
1131 next if $running;
1132 } elsif ($opt eq 'rootfs') {
b51a98d4 1133 die "implement me"
93285df8
DM
1134 } else {
1135 die "implement me"
1136 }
706c9791 1137 write_config($vmid, $conf) if $running;
93285df8
DM
1138 }
1139 }
1140
be6383d7
WB
1141 # There's no separate swap size to configure, there's memory and "total"
1142 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1143 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1144 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1145 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1146
1147 $wanted_memory //= ($conf->{memory} || 512);
1148 $wanted_swap //= ($conf->{swap} || 0);
1149
1150 my $total = $wanted_memory + $wanted_swap;
1151 if ($running) {
1152 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1153 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1154 }
27916659
DM
1155 $conf->{memory} = $wanted_memory;
1156 $conf->{swap} = $wanted_swap;
1157
706c9791 1158 write_config($vmid, $conf) if $running;
be6383d7
WB
1159 }
1160
93285df8
DM
1161 foreach my $opt (keys %$param) {
1162 my $value = $param->{$opt};
1163 if ($opt eq 'hostname') {
27916659 1164 $conf->{$opt} = $value;
a99b3509 1165 } elsif ($opt eq 'onboot') {
27916659 1166 $conf->{$opt} = $value ? 1 : 0;
a3249355 1167 } elsif ($opt eq 'startup') {
27916659 1168 $conf->{$opt} = $value;
40603eb3 1169 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
e576f689
DM
1170 $conf->{$opt} = $value;
1171 push @nohotplug, $opt;
1172 next if $running;
ffa1d001 1173 } elsif ($opt eq 'nameserver') {
117636e5 1174 my $list = verify_nameserver_list($value);
27916659 1175 $conf->{$opt} = $list;
bf0b8c43
AD
1176 push @nohotplug, $opt;
1177 next if $running;
ffa1d001 1178 } elsif ($opt eq 'searchdomain') {
117636e5 1179 my $list = verify_searchdomain_list($value);
27916659 1180 $conf->{$opt} = $list;
bf0b8c43
AD
1181 push @nohotplug, $opt;
1182 next if $running;
45573f7c 1183 } elsif ($opt eq 'cpulimit') {
27916659
DM
1184 $conf->{$opt} = $value;
1185 push @nohotplug, $opt; # fixme: hotplug
1186 next;
b80dd50a 1187 } elsif ($opt eq 'cpuunits') {
27916659 1188 $conf->{$opt} = $value;
bf0b8c43 1189 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1190 } elsif ($opt eq 'description') {
27916659 1191 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1192 } elsif ($opt =~ m/^net(\d+)$/) {
1193 my $netid = $1;
a16d94c8 1194 my $net = parse_lxc_network($value);
27916659
DM
1195 if (!$running) {
1196 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1197 } else {
bedeaaf1
AD
1198 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1199 }
7e806596
AG
1200 } elsif ($opt eq 'protection') {
1201 $conf->{$opt} = $value ? 1 : 0;
4fee75fd
WB
1202 } elsif ($opt =~ m/^mp(\d+)$/) {
1203 $conf->{$opt} = $value;
1204 push @$new_disks, $opt;
1205 push @nohotplug, $opt;
1206 next;
1207 } elsif ($opt eq 'rootfs') {
b51a98d4 1208 die "implement me: $opt";
93285df8 1209 } else {
a92f66c9 1210 die "implement me: $opt";
93285df8 1211 }
706c9791 1212 write_config($vmid, $conf) if $running;
93285df8 1213 }
bf0b8c43 1214
5cfa0567
DM
1215 if ($running && scalar(@nohotplug)) {
1216 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1217 }
4fee75fd
WB
1218
1219 if (@$new_disks) {
1220 my $storage_cfg = PVE::Storage::config();
6c871c36 1221 create_disks($storage_cfg, $vmid, $conf, $conf);
4fee75fd
WB
1222 mount_all($vmid, $storage_cfg, $conf, $new_disks, 1);
1223 umount_all($vmid, $storage_cfg, $conf, 0);
1224 }
93285df8 1225}
c325b32f 1226
6f035afe
DM
1227sub has_dev_console {
1228 my ($conf) = @_;
1229
1230 return !(defined($conf->{console}) && !$conf->{console});
1231}
1232
0d0ca400
DM
1233sub get_tty_count {
1234 my ($conf) = @_;
1235
1236 return $conf->{tty} // $confdesc->{tty}->{default};
1237}
1238
aca816ad
DM
1239sub get_cmode {
1240 my ($conf) = @_;
1241
1242 return $conf->{cmode} // $confdesc->{cmode}->{default};
1243}
1244
1245sub get_console_command {
1246 my ($vmid, $conf) = @_;
1247
1248 my $cmode = get_cmode($conf);
1249
1250 if ($cmode eq 'console') {
1251 return ['lxc-console', '-n', $vmid, '-t', 0];
1252 } elsif ($cmode eq 'tty') {
1253 return ['lxc-console', '-n', $vmid];
1254 } elsif ($cmode eq 'shell') {
1255 return ['lxc-attach', '--clear-env', '-n', $vmid];
1256 } else {
1257 die "internal error";
1258 }
1259}
1260
c325b32f
DM
1261sub get_primary_ips {
1262 my ($conf) = @_;
1263
1264 # return data from net0
cbb03fea 1265
27916659 1266 return undef if !defined($conf->{net0});
a16d94c8 1267 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1268
1269 my $ipv4 = $net->{ip};
db78a181
WB
1270 if ($ipv4) {
1271 if ($ipv4 =~ /^(dhcp|manual)$/) {
1272 $ipv4 = undef
1273 } else {
1274 $ipv4 =~ s!/\d+$!!;
1275 }
1276 }
65e5eaa3 1277 my $ipv6 = $net->{ip6};
db78a181
WB
1278 if ($ipv6) {
1279 if ($ipv6 =~ /^(dhcp|manual)$/) {
1280 $ipv6 = undef;
1281 } else {
1282 $ipv6 =~ s!/\d+$!!;
1283 }
1284 }
cbb03fea 1285
c325b32f
DM
1286 return ($ipv4, $ipv6);
1287}
148d1cb4 1288
ef241384 1289
27916659 1290sub destroy_lxc_container {
148d1cb4
DM
1291 my ($storage_cfg, $vmid, $conf) = @_;
1292
db8989e1
WB
1293 foreach_mountpoint($conf, sub {
1294 my ($ms, $mountpoint) = @_;
1295 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
1296 PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
1297 });
1298
27916659
DM
1299 rmdir "/var/lib/lxc/$vmid/rootfs";
1300 unlink "/var/lib/lxc/$vmid/config";
1301 rmdir "/var/lib/lxc/$vmid";
1302 destroy_config($vmid);
1303
1304 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1305 #PVE::Tools::run_command($cmd);
148d1cb4 1306}
68fba17b 1307
ef241384 1308sub vm_stop_cleanup {
5fa890f0 1309 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
ef241384
DM
1310
1311 eval {
1312 if (!$keepActive) {
bf9d912c 1313
09aa32fd 1314 my $vollist = get_vm_volumes($conf);
a8b6b8a7 1315 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1316 }
1317 };
1318 warn $@ if $@; # avoid errors - just warn
1319}
1320
93cdbbfb
AD
1321my $safe_num_ne = sub {
1322 my ($a, $b) = @_;
1323
1324 return 0 if !defined($a) && !defined($b);
1325 return 1 if !defined($a);
1326 return 1 if !defined($b);
1327
1328 return $a != $b;
1329};
1330
1331my $safe_string_ne = sub {
1332 my ($a, $b) = @_;
1333
1334 return 0 if !defined($a) && !defined($b);
1335 return 1 if !defined($a);
1336 return 1 if !defined($b);
1337
1338 return $a ne $b;
1339};
1340
1341sub update_net {
bedeaaf1 1342 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1343
18862537
WB
1344 if ($newnet->{type} ne 'veth') {
1345 # for when there are physical interfaces
1346 die "cannot update interface of type $newnet->{type}";
1347 }
1348
1349 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1350 my $eth = $newnet->{name};
1351
18862537
WB
1352 if (my $oldnetcfg = $conf->{$opt}) {
1353 my $oldnet = parse_lxc_network($oldnetcfg);
1354
1355 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1356 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1357
18862537 1358 PVE::Network::veth_delete($veth);
bedeaaf1 1359 delete $conf->{$opt};
706c9791 1360 write_config($vmid, $conf);
93cdbbfb 1361
18862537 1362 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1363
18862537
WB
1364 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1365 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1366 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1367
18862537 1368 if ($oldnet->{bridge}) {
bedeaaf1 1369 PVE::Network::tap_unplug($veth);
18862537
WB
1370 foreach (qw(bridge tag firewall)) {
1371 delete $oldnet->{$_};
1372 }
1373 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1374 write_config($vmid, $conf);
bedeaaf1 1375 }
93cdbbfb 1376
18862537
WB
1377 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1378 foreach (qw(bridge tag firewall)) {
1379 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1380 }
1381 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1382 write_config($vmid, $conf);
93cdbbfb
AD
1383 }
1384 } else {
18862537 1385 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1386 }
1387
bedeaaf1 1388 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1389}
1390
1391sub hotplug_net {
18862537 1392 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1393
18862537 1394 my $veth = "veth${vmid}i${netid}";
cbb03fea 1395 my $vethpeer = $veth . "p";
93cdbbfb
AD
1396 my $eth = $newnet->{name};
1397
1398 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1399 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1400
cbb03fea 1401 # attach peer in container
93cdbbfb
AD
1402 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1403 PVE::Tools::run_command($cmd);
1404
cbb03fea 1405 # link up peer in container
93cdbbfb
AD
1406 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1407 PVE::Tools::run_command($cmd);
bedeaaf1 1408
18862537
WB
1409 my $done = { type => 'veth' };
1410 foreach (qw(bridge tag firewall hwaddr name)) {
1411 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1412 }
1413 $conf->{$opt} = print_lxc_network($done);
bedeaaf1 1414
706c9791 1415 write_config($vmid, $conf);
93cdbbfb
AD
1416}
1417
68a05bb3 1418sub update_ipconfig {
bedeaaf1
AD
1419 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1420
f2104b80 1421 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
bedeaaf1 1422
18862537 1423 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1424 my $deleted = [];
1425 my $added = [];
8d723477
WB
1426 my $nscmd = sub {
1427 my $cmdargs = shift;
1428 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1429 };
8d723477 1430 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1431
84e0c123 1432 my $change_ip_config = sub {
f39002a6
DM
1433 my ($ipversion) = @_;
1434
1435 my $family_opt = "-$ipversion";
1436 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1437 my $gw= "gw$suffix";
1438 my $ip= "ip$suffix";
bedeaaf1 1439
6178b0dd
WB
1440 my $newip = $newnet->{$ip};
1441 my $newgw = $newnet->{$gw};
1442 my $oldip = $optdata->{$ip};
1443
1444 my $change_ip = &$safe_string_ne($oldip, $newip);
1445 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1446
84e0c123 1447 return if !$change_ip && !$change_gw;
68a05bb3 1448
84e0c123 1449 # step 1: add new IP, if this fails we cancel
6178b0dd 1450 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1451 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1452 if (my $err = $@) {
1453 warn $err;
1454 return;
1455 }
bedeaaf1 1456 }
bedeaaf1 1457
84e0c123
WB
1458 # step 2: replace gateway
1459 # If this fails we delete the added IP and cancel.
1460 # If it succeeds we save the config and delete the old IP, ignoring
1461 # errors. The config is then saved.
1462 # Note: 'ip route replace' can add
1463 if ($change_gw) {
6178b0dd 1464 if ($newgw) {
8d723477 1465 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1466 if (my $err = $@) {
1467 warn $err;
1468 # the route was not replaced, the old IP is still available
1469 # rollback (delete new IP) and cancel
1470 if ($change_ip) {
8d723477 1471 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1472 warn $@ if $@; # no need to die here
1473 }
1474 return;
1475 }
1476 } else {
8d723477 1477 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1478 # if the route was not deleted, the guest might have deleted it manually
1479 # warn and continue
1480 warn $@ if $@;
1481 }
2bfd1615 1482 }
2bfd1615 1483
6178b0dd 1484 # from this point on we save the configuration
84e0c123 1485 # step 3: delete old IP ignoring errors
6178b0dd 1486 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1487 # We need to enable promote_secondaries, otherwise our newly added
1488 # address will be removed along with the old one.
1489 my $promote = 0;
1490 eval {
1491 if ($ipversion == 4) {
1492 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1493 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1494 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1495 }
1496 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1497 };
84e0c123 1498 warn $@ if $@; # no need to die here
8d723477
WB
1499
1500 if ($ipversion == 4) {
1501 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1502 }
bedeaaf1
AD
1503 }
1504
84e0c123
WB
1505 foreach my $property ($ip, $gw) {
1506 if ($newnet->{$property}) {
1507 $optdata->{$property} = $newnet->{$property};
1508 } else {
1509 delete $optdata->{$property};
1510 }
bedeaaf1 1511 }
18862537 1512 $conf->{$opt} = print_lxc_network($optdata);
706c9791 1513 write_config($vmid, $conf);
84e0c123
WB
1514 $lxc_setup->setup_network($conf);
1515 };
bedeaaf1 1516
f39002a6
DM
1517 &$change_ip_config(4);
1518 &$change_ip_config(6);
489e960d
WL
1519
1520}
1521
a92f66c9
WL
1522# Internal snapshots
1523
1524# NOTE: Snapshot create/delete involves several non-atomic
1525# action, and can take a long time.
1526# So we try to avoid locking the file and use 'lock' variable
1527# inside the config file instead.
1528
1529my $snapshot_copy_config = sub {
1530 my ($source, $dest) = @_;
1531
1532 foreach my $k (keys %$source) {
1533 next if $k eq 'snapshots';
09d3ec42
DM
1534 next if $k eq 'snapstate';
1535 next if $k eq 'snaptime';
1536 next if $k eq 'vmstate';
1537 next if $k eq 'lock';
a92f66c9 1538 next if $k eq 'digest';
09d3ec42 1539 next if $k eq 'description';
a92f66c9
WL
1540
1541 $dest->{$k} = $source->{$k};
1542 }
1543};
1544
1545my $snapshot_prepare = sub {
1546 my ($vmid, $snapname, $comment) = @_;
1547
1548 my $snap;
1549
1550 my $updatefn = sub {
1551
1552 my $conf = load_config($vmid);
1553
bb1ac2de
DM
1554 die "you can't take a snapshot if it's a template\n"
1555 if is_template($conf);
1556
a92f66c9
WL
1557 check_lock($conf);
1558
09d3ec42 1559 $conf->{lock} = 'snapshot';
a92f66c9
WL
1560
1561 die "snapshot name '$snapname' already used\n"
1562 if defined($conf->{snapshots}->{$snapname});
1563
1564 my $storecfg = PVE::Storage::config();
1565 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1566
1567 $snap = $conf->{snapshots}->{$snapname} = {};
1568
1569 &$snapshot_copy_config($conf, $snap);
1570
09d3ec42
DM
1571 $snap->{'snapstate'} = "prepare";
1572 $snap->{'snaptime'} = time();
1573 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1574 $conf->{snapshots}->{$snapname} = $snap;
1575
706c9791 1576 write_config($vmid, $conf);
a92f66c9
WL
1577 };
1578
1579 lock_container($vmid, 10, $updatefn);
1580
1581 return $snap;
1582};
1583
1584my $snapshot_commit = sub {
1585 my ($vmid, $snapname) = @_;
1586
1587 my $updatefn = sub {
1588
1589 my $conf = load_config($vmid);
1590
1591 die "missing snapshot lock\n"
09d3ec42 1592 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1593
27916659 1594 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1595 if !defined($conf->{snapshots}->{$snapname});
1596
1597 die "wrong snapshot state\n"
09d3ec42
DM
1598 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1599 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1600
09d3ec42
DM
1601 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1602 delete $conf->{lock};
1603 $conf->{parent} = $snapname;
a92f66c9 1604
706c9791 1605 write_config($vmid, $conf);
a92f66c9
WL
1606 };
1607
1608 lock_container($vmid, 10 ,$updatefn);
1609};
1610
1611sub has_feature {
1612 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1613
a92f66c9 1614 my $err;
09d3ec42 1615
8bf50651
DM
1616 foreach_mountpoint($conf, sub {
1617 my ($ms, $mountpoint) = @_;
1618
2c3ed8c4
DM
1619 return if $err; # skip further test
1620
8bf50651
DM
1621 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1622
1623 # TODO: implement support for mountpoints
1624 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1625 if $ms ne 'rootfs';
1626 });
a92f66c9
WL
1627
1628 return $err ? 0 : 1;
1629}
1630
489e960d
WL
1631sub snapshot_create {
1632 my ($vmid, $snapname, $comment) = @_;
1633
a92f66c9
WL
1634 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1635
09d3ec42 1636 my $conf = load_config($vmid);
a92f66c9
WL
1637
1638 my $cmd = "/usr/bin/lxc-freeze -n $vmid";
1639 my $running = check_running($vmid);
1640 eval {
1641 if ($running) {
1642 PVE::Tools::run_command($cmd);
1643 };
1644
1645 my $storecfg = PVE::Storage::config();
706c9791 1646 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
09d3ec42 1647 my $volid = $rootinfo->{volume};
a92f66c9
WL
1648
1649 $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
1650 if ($running) {
1651 PVE::Tools::run_command($cmd);
1652 };
489e960d 1653
a92f66c9
WL
1654 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1655 &$snapshot_commit($vmid, $snapname);
1656 };
1657 if(my $err = $@) {
31429832 1658 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1659 die "$err\n";
1660 }
68a05bb3
AD
1661}
1662
57ccb3f8
WL
1663sub snapshot_delete {
1664 my ($vmid, $snapname, $force) = @_;
1665
31429832
WL
1666 my $snap;
1667
1668 my $conf;
1669
1670 my $updatefn = sub {
1671
1672 $conf = load_config($vmid);
1673
bb1ac2de
DM
1674 die "you can't delete a snapshot if vm is a template\n"
1675 if is_template($conf);
1676
31429832
WL
1677 $snap = $conf->{snapshots}->{$snapname};
1678
1679 check_lock($conf);
1680
1681 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1682
09d3ec42 1683 $snap->{snapstate} = 'delete';
31429832 1684
706c9791 1685 write_config($vmid, $conf);
31429832
WL
1686 };
1687
1688 lock_container($vmid, 10, $updatefn);
1689
1690 my $storecfg = PVE::Storage::config();
1691
1692 my $del_snap = sub {
1693
1694 check_lock($conf);
1695
09d3ec42
DM
1696 if ($conf->{parent} eq $snapname) {
1697 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1698 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1699 } else {
09d3ec42 1700 delete $conf->{parent};
31429832
WL
1701 }
1702 }
1703
1704 delete $conf->{snapshots}->{$snapname};
1705
706c9791 1706 write_config($vmid, $conf);
31429832
WL
1707 };
1708
09d3ec42 1709 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
706c9791 1710 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42 1711 my $volid = $rootinfo->{volume};
31429832
WL
1712
1713 eval {
1714 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1715 };
1716 my $err = $@;
1717
1718 if(!$err || ($err && $force)) {
1719 lock_container($vmid, 10, $del_snap);
1720 if ($err) {
1721 die "Can't delete snapshot: $vmid $snapname $err\n";
1722 }
1723 }
57ccb3f8
WL
1724}
1725
723157f6
WL
1726sub snapshot_rollback {
1727 my ($vmid, $snapname) = @_;
1728
6860ba0c
WL
1729 my $storecfg = PVE::Storage::config();
1730
1731 my $conf = load_config($vmid);
1732
bb1ac2de
DM
1733 die "you can't rollback if vm is a template\n" if is_template($conf);
1734
6860ba0c
WL
1735 my $snap = $conf->{snapshots}->{$snapname};
1736
1737 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1738
09d3ec42 1739 my $rootfs = $snap->{rootfs};
706c9791 1740 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42
DM
1741 my $volid = $rootinfo->{volume};
1742
1743 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1744
1745 my $updatefn = sub {
1746
09d3ec42
DM
1747 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1748 if $snap->{snapstate};
6860ba0c
WL
1749
1750 check_lock($conf);
6860ba0c 1751
b935932a 1752 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1753
1754 die "unable to rollback vm $vmid: vm is running\n"
1755 if check_running($vmid);
1756
09d3ec42 1757 $conf->{lock} = 'rollback';
6860ba0c
WL
1758
1759 my $forcemachine;
1760
1761 # copy snapshot config to current config
1762
1763 my $tmp_conf = $conf;
1764 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1765 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1766 delete $conf->{snaptime};
1767 delete $conf->{snapname};
1768 $conf->{parent} = $snapname;
6860ba0c 1769
706c9791 1770 write_config($vmid, $conf);
6860ba0c
WL
1771 };
1772
1773 my $unlockfn = sub {
09d3ec42 1774 delete $conf->{lock};
706c9791 1775 write_config($vmid, $conf);
6860ba0c
WL
1776 };
1777
1778 lock_container($vmid, 10, $updatefn);
1779
09d3ec42 1780 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1781
1782 lock_container($vmid, 5, $unlockfn);
723157f6 1783}
b935932a 1784
bb1ac2de
DM
1785sub template_create {
1786 my ($vmid, $conf) = @_;
1787
1788 my $storecfg = PVE::Storage::config();
1789
706c9791 1790 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
bb1ac2de
DM
1791 my $volid = $rootinfo->{volume};
1792
1793 die "Template feature is not available for '$volid'\n"
1794 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1795
1796 PVE::Storage::activate_volumes($storecfg, [$volid]);
1797
1798 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1799 $rootinfo->{volume} = $template_volid;
4fee75fd 1800 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
bb1ac2de
DM
1801
1802 write_config($vmid, $conf);
1803}
1804
1805sub is_template {
1806 my ($conf) = @_;
1807
1808 return 1 if defined $conf->{template} && $conf->{template} == 1;
1809}
1810
9622e848
DM
1811sub mountpoint_names {
1812 my ($reverse) = @_;
ced7fddb 1813
9622e848 1814 my @names = ('rootfs');
eaebef36
DM
1815
1816 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
9622e848
DM
1817 push @names, "mp$i";
1818 }
1819
1820 return $reverse ? reverse @names : @names;
1821}
1822
1823sub foreach_mountpoint_full {
1824 my ($conf, $reverse, $func) = @_;
1825
1826 foreach my $key (mountpoint_names($reverse)) {
1827 my $value = $conf->{$key};
1828 next if !defined($value);
1829 my $mountpoint = parse_ct_mountpoint($value);
1830 $mountpoint->{mp} = '/' if $key eq 'rootfs'; # just to be sure
eaebef36 1831 &$func($key, $mountpoint);
ced7fddb
AD
1832 }
1833}
1834
9622e848
DM
1835sub foreach_mountpoint {
1836 my ($conf, $func) = @_;
1837
1838 foreach_mountpoint_full($conf, 0, $func);
1839}
1840
1841sub foreach_mountpoint_reverse {
1842 my ($conf, $func) = @_;
1843
1844 foreach_mountpoint_full($conf, 1, $func);
1845}
1846
52389a07
DM
1847sub check_ct_modify_config_perm {
1848 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1849
1850 return 1 if $authuser ne 'root@pam';
1851
1852 foreach my $opt (@$key_list) {
1853
1854 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1855 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1856 } elsif ($opt eq 'disk') {
1857 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1858 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1859 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1860 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1861 $opt eq 'searchdomain' || $opt eq 'hostname') {
1862 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1863 } else {
1864 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1865 }
1866 }
1867
1868 return 1;
1869}
1870
9622e848 1871sub umount_all {
da629848 1872 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
9622e848
DM
1873
1874 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1875 my $volid_list = get_vm_volumes($conf);
1876
1877 foreach_mountpoint_reverse($conf, sub {
1878 my ($ms, $mountpoint) = @_;
1879
1880 my $volid = $mountpoint->{volume};
1881 my $mount = $mountpoint->{mp};
1882
1883 return if !$volid || !$mount;
1884
d18f96b4 1885 my $mount_path = "$rootdir/$mount";
f845a93d 1886 $mount_path =~ s!/+!/!g;
9622e848 1887
228a5a1d
WL
1888 return if !PVE::ProcFSTools::is_mounted($mount_path);
1889
9622e848 1890 eval {
d18f96b4 1891 PVE::Tools::run_command(['umount', '-d', $mount_path]);
9622e848
DM
1892 };
1893 if (my $err = $@) {
1894 if ($noerr) {
1895 warn $err;
1896 } else {
1897 die $err;
1898 }
1899 }
1900 });
9622e848
DM
1901}
1902
1903sub mount_all {
4fee75fd 1904 my ($vmid, $storage_cfg, $conf, $mkdirs) = @_;
9622e848
DM
1905
1906 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1adc7e53 1907 File::Path::make_path($rootdir);
9622e848
DM
1908
1909 my $volid_list = get_vm_volumes($conf);
1910 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
1911
1912 eval {
9622e848
DM
1913 foreach_mountpoint($conf, sub {
1914 my ($ms, $mountpoint) = @_;
1915
1916 my $volid = $mountpoint->{volume};
1917 my $mount = $mountpoint->{mp};
1918
1919 return if !$volid || !$mount;
1920
1921 my $image_path = PVE::Storage::path($storage_cfg, $volid);
1922 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1923 PVE::Storage::parse_volname($storage_cfg, $volid);
1924
1925 die "unable to mount base volume - internal error" if $isBase;
1926
4fee75fd 1927 File::Path::make_path "$rootdir/$mount" if $mkdirs;
da629848 1928 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
9622e848
DM
1929 });
1930 };
1931 if (my $err = $@) {
1932 warn "mounting container failed - $err";
1933 umount_all($vmid, $storage_cfg, $conf, 1);
9622e848
DM
1934 }
1935
da629848 1936 return $rootdir;
9622e848
DM
1937}
1938
1939
b15c75fc 1940sub mountpoint_mount_path {
da629848 1941 my ($mountpoint, $storage_cfg, $snapname) = @_;
b15c75fc 1942
da629848 1943 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
b15c75fc 1944}
cc6b0307 1945
b15c75fc 1946# use $rootdir = undef to just return the corresponding mount path
cc6b0307 1947sub mountpoint_mount {
da629848 1948 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
cc6b0307
AD
1949
1950 my $volid = $mountpoint->{volume};
1951 my $mount = $mountpoint->{mp};
b15c75fc 1952
cc6b0307
AD
1953 return if !$volid || !$mount;
1954
b15c75fc
DM
1955 my $mount_path;
1956
1957 if (defined($rootdir)) {
1958 $rootdir =~ s!/+$!!;
1959 $mount_path = "$rootdir/$mount";
f845a93d 1960 $mount_path =~ s!/+!/!g;
b15c75fc 1961 File::Path::mkpath($mount_path);
116ce06f 1962 }
b15c75fc
DM
1963
1964 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
cc6b0307 1965
b15c75fc 1966 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
cc6b0307 1967
b15c75fc
DM
1968 if ($storage) {
1969
1970 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
1971 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
1972
1973 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
1974 PVE::Storage::parse_volname($storage_cfg, $volid);
1975
1976 if ($format eq 'subvol') {
30de33be
DM
1977 if ($mount_path) {
1978 if ($snapname) {
1979 if ($scfg->{type} eq 'zfspool') {
1980 my $path_arg = $path;
1981 $path_arg =~ s!^/+!!;
1982 PVE::Tools::run_command(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
1983 } else {
1984 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
1985 }
b15c75fc 1986 } else {
30de33be
DM
1987 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
1988 }
b15c75fc 1989 }
30de33be 1990 return wantarray ? ($path, 0) : $path;
b15c75fc 1991 } elsif ($format eq 'raw') {
30de33be 1992 my $use_loopdev = 0;
da629848 1993 my @extra_opts;
b15c75fc 1994 if ($scfg->{path}) {
da629848 1995 push @extra_opts, '-o', 'loop';
30de33be 1996 $use_loopdev = 1;
23e3abef 1997 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
b15c75fc
DM
1998 # do nothing
1999 } else {
2000 die "unsupported storage type '$scfg->{type}'\n";
2001 }
30de33be
DM
2002 if ($mount_path) {
2003 if ($isBase || defined($snapname)) {
2004 PVE::Tools::run_command(['mount', '-o', "ro", @extra_opts, $path, $mount_path]);
2005 } else {
2006 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2007 }
b15c75fc 2008 }
30de33be 2009 return wantarray ? ($path, $use_loopdev) : $path;
b15c75fc
DM
2010 } else {
2011 die "unsupported image format '$format'\n";
2012 }
2013 } elsif ($volid =~ m|^/dev/.+|) {
2014 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
30de33be 2015 return wantarray ? ($volid, 0) : $volid;
b15c75fc
DM
2016 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2017 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
30de33be 2018 return wantarray ? ($volid, 0) : $volid;
b15c75fc
DM
2019 }
2020
2021 die "unsupported storage";
cc6b0307
AD
2022}
2023
9205e9d0
AD
2024sub get_vm_volumes {
2025 my ($conf, $excludes) = @_;
2026
2027 my $vollist = [];
2028
706c9791 2029 foreach_mountpoint($conf, sub {
9205e9d0
AD
2030 my ($ms, $mountpoint) = @_;
2031
2032 return if $excludes && $ms eq $excludes;
2033
2034 my $volid = $mountpoint->{volume};
2035
2036 return if !$volid || $volid =~ m|^/|;
2037
2038 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2039 return if !$sid;
2040
2041 push @$vollist, $volid;
2042 });
2043
2044 return $vollist;
2045}
2046
6c871c36
DM
2047sub mkfs {
2048 my ($dev) = @_;
2049
2050 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2051}
2052
2053sub format_disk {
2054 my ($storage_cfg, $volid) = @_;
2055
2056 if ($volid =~ m!^/dev/.+!) {
2057 mkfs($volid);
2058 return;
2059 }
2060
2061 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2062
2063 die "cannot format volume '$volid' with no storage\n" if !$storage;
2064
08ca136d
DM
2065 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2066
6c871c36
DM
2067 my $path = PVE::Storage::path($storage_cfg, $volid);
2068
2069 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2070 PVE::Storage::parse_volname($storage_cfg, $volid);
2071
2072 die "cannot format volume '$volid' (format == $format)\n"
2073 if $format ne 'raw';
2074
2075 mkfs($path);
2076}
2077
2078sub destroy_disks {
2079 my ($storecfg, $vollist) = @_;
2080
2081 foreach my $volid (@$vollist) {
2082 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2083 warn $@ if $@;
2084 }
2085}
2086
2087sub create_disks {
2088 my ($storecfg, $vmid, $settings, $conf) = @_;
2089
2090 my $vollist = [];
2091
2092 eval {
2093 foreach_mountpoint($settings, sub {
2094 my ($ms, $mountpoint) = @_;
2095
2096 my $volid = $mountpoint->{volume};
2097 my $mp = $mountpoint->{mp};
2098
2099 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2100
2101 return if !$storage;
2102
2103 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
8ed5ff9d 2104 my ($storeid, $size_gb) = ($1, $2);
6c871c36 2105
8ed5ff9d 2106 my $size_kb = int(${size_gb}*1024) * 1024;
6c871c36
DM
2107
2108 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2109 # fixme: use better naming ct-$vmid-disk-X.raw?
2110
2111 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
8ed5ff9d 2112 if ($size_kb > 0) {
6c871c36 2113 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
8ed5ff9d 2114 undef, $size_kb);
6c871c36
DM
2115 format_disk($storecfg, $volid);
2116 } else {
2117 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2118 undef, 0);
2119 }
2120 } elsif ($scfg->{type} eq 'zfspool') {
2121
2122 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
8ed5ff9d 2123 undef, $size_kb);
23e3abef 2124 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
6c871c36 2125
8ed5ff9d 2126 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
6c871c36
DM
2127 format_disk($storecfg, $volid);
2128
2129 } elsif ($scfg->{type} eq 'rbd') {
2130
2131 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
8ed5ff9d 2132 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
6c871c36
DM
2133 format_disk($storecfg, $volid);
2134 } else {
2135 die "unable to create containers on storage type '$scfg->{type}'\n";
2136 }
2137 push @$vollist, $volid;
8ed5ff9d 2138 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
ab4232be 2139 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
6c871c36
DM
2140 } else {
2141 # use specified/existing volid
2142 }
2143 });
2144 };
2145 # free allocated images on error
2146 if (my $err = $@) {
2147 destroy_disks($storecfg, $vollist);
2148 die $err;
2149 }
2150 return $vollist;
2151}
2152
68e8f3c5
DM
2153# bash completion helper
2154
2155sub complete_os_templates {
2156 my ($cmdname, $pname, $cvalue) = @_;
2157
2158 my $cfg = PVE::Storage::config();
2159
9e9bc3a6 2160 my $storeid;
68e8f3c5
DM
2161
2162 if ($cvalue =~ m/^([^:]+):/) {
2163 $storeid = $1;
2164 }
2165
2166 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2167 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2168
2169 my $res = [];
2170 foreach my $id (keys %$data) {
2171 foreach my $item (@{$data->{$id}}) {
2172 push @$res, $item->{volid} if defined($item->{volid});
2173 }
2174 }
2175
2176 return $res;
2177}
2178
68e8f3c5
DM
2179my $complete_ctid_full = sub {
2180 my ($running) = @_;
2181
2182 my $idlist = vmstatus();
2183
2184 my $active_hash = list_active_containers();
2185
2186 my $res = [];
2187
2188 foreach my $id (keys %$idlist) {
2189 my $d = $idlist->{$id};
2190 if (defined($running)) {
2191 next if $d->{template};
2192 next if $running && !$active_hash->{$id};
2193 next if !$running && $active_hash->{$id};
2194 }
2195 push @$res, $id;
2196
2197 }
2198 return $res;
2199};
2200
2201sub complete_ctid {
2202 return &$complete_ctid_full();
2203}
2204
2205sub complete_ctid_stopped {
2206 return &$complete_ctid_full(0);
2207}
2208
2209sub complete_ctid_running {
2210 return &$complete_ctid_full(1);
2211}
2212
f76a2828 22131;