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