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