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