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