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