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