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