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