]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
bump version to 1.0-2
[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 => {
42 type => 'string',
43 format_description => 'DiskSize',
44 pattern => '\d+[TGMK]?',
45 description => 'Volume size (read only value).',
46 optional => 1,
47 },
48};
822de0c3 49
27916659 50PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
769fbfab 51 type => 'string', format => $rootfs_desc,
8fbd2935 52 description => "Use volume as container root.",
27916659
DM
53 optional => 1,
54});
55
52389a07
DM
56PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
57 description => "The name of the snapshot.",
58 type => 'string', format => 'pve-configid',
59 maxLength => 40,
60});
61
27916659 62my $confdesc = {
09d3ec42
DM
63 lock => {
64 optional => 1,
65 type => 'string',
66 description => "Lock/unlock the VM.",
67 enum => [qw(migrate backup snapshot rollback)],
68 },
27916659
DM
69 onboot => {
70 optional => 1,
71 type => 'boolean',
72 description => "Specifies whether a VM will be started during system bootup.",
73 default => 0,
117636e5 74 },
27916659 75 startup => get_standard_option('pve-startup-order'),
bb1ac2de
DM
76 template => {
77 optional => 1,
78 type => 'boolean',
79 description => "Enable/disable Template.",
80 default => 0,
81 },
27916659
DM
82 arch => {
83 optional => 1,
84 type => 'string',
85 enum => ['amd64', 'i386'],
86 description => "OS architecture type.",
87 default => 'amd64',
117636e5 88 },
27916659
DM
89 ostype => {
90 optional => 1,
91 type => 'string',
c0821a36 92 enum => ['debian', 'ubuntu', 'centos', 'archlinux'],
27916659 93 description => "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
a3249355 94 },
4f958489
DM
95 console => {
96 optional => 1,
97 type => 'boolean',
98 description => "Attach a console device (/dev/console) to the container.",
99 default => 1,
100 },
27916659
DM
101 tty => {
102 optional => 1,
103 type => 'integer',
104 description => "Specify the number of tty available to the container",
105 minimum => 0,
106 maximum => 6,
0d0ca400 107 default => 2,
611fe3aa 108 },
27916659
DM
109 cpulimit => {
110 optional => 1,
111 type => 'number',
112 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.",
113 minimum => 0,
114 maximum => 128,
115 default => 0,
116 },
117 cpuunits => {
118 optional => 1,
119 type => 'integer',
120 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.",
121 minimum => 0,
122 maximum => 500000,
81bee809 123 default => 1024,
27916659
DM
124 },
125 memory => {
126 optional => 1,
127 type => 'integer',
128 description => "Amount of RAM for the VM in MB.",
129 minimum => 16,
130 default => 512,
131 },
132 swap => {
133 optional => 1,
134 type => 'integer',
135 description => "Amount of SWAP for the VM in MB.",
136 minimum => 0,
137 default => 512,
138 },
139 hostname => {
140 optional => 1,
141 description => "Set a host name for the container.",
159aad3e 142 type => 'string', format => 'dns-name',
27916659
DM
143 maxLength => 255,
144 },
145 description => {
146 optional => 1,
147 type => 'string',
148 description => "Container description. Only used on the configuration web interface.",
149 },
150 searchdomain => {
151 optional => 1,
159aad3e 152 type => 'string', format => 'dns-name-list',
27916659
DM
153 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
154 },
155 nameserver => {
156 optional => 1,
159aad3e 157 type => 'string', format => 'address-list',
27916659
DM
158 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.",
159 },
160 rootfs => get_standard_option('pve-ct-rootfs'),
09d3ec42
DM
161 parent => {
162 optional => 1,
163 type => 'string', format => 'pve-configid',
164 maxLength => 40,
165 description => "Parent snapshot name. This is used internally, and should not be modified.",
166 },
167 snaptime => {
168 optional => 1,
169 description => "Timestamp for snapshots.",
170 type => 'integer',
171 minimum => 0,
172 },
aca816ad
DM
173 cmode => {
174 optional => 1,
175 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).",
176 type => 'string',
177 enum => ['shell', 'console', 'tty'],
178 default => 'tty',
179 },
7e806596
AG
180 protection => {
181 optional => 1,
182 type => 'boolean',
e22af68f 183 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
184 default => 0,
185 },
f76a2828
DM
186};
187
e576f689
DM
188my $valid_lxc_conf_keys = {
189 'lxc.include' => 1,
190 'lxc.arch' => 1,
191 'lxc.utsname' => 1,
192 'lxc.haltsignal' => 1,
193 'lxc.rebootsignal' => 1,
194 'lxc.stopsignal' => 1,
195 'lxc.init_cmd' => 1,
196 'lxc.network.type' => 1,
197 'lxc.network.flags' => 1,
198 'lxc.network.link' => 1,
199 'lxc.network.mtu' => 1,
200 'lxc.network.name' => 1,
201 'lxc.network.hwaddr' => 1,
202 'lxc.network.ipv4' => 1,
203 'lxc.network.ipv4.gateway' => 1,
204 'lxc.network.ipv6' => 1,
205 'lxc.network.ipv6.gateway' => 1,
206 'lxc.network.script.up' => 1,
207 'lxc.network.script.down' => 1,
208 'lxc.pts' => 1,
209 'lxc.console.logfile' => 1,
210 'lxc.console' => 1,
211 'lxc.tty' => 1,
212 'lxc.devttydir' => 1,
213 'lxc.hook.autodev' => 1,
214 'lxc.autodev' => 1,
215 'lxc.kmsg' => 1,
216 'lxc.mount' => 1,
217 'lxc.mount.entry' => 1,
218 'lxc.mount.auto' => 1,
219 'lxc.rootfs' => 1,
220 'lxc.rootfs.mount' => 1,
221 'lxc.rootfs.options' => 1,
222 # lxc.cgroup.*
223 'lxc.cap.drop' => 1,
224 'lxc.cap.keep' => 1,
225 'lxc.aa_profile' => 1,
226 'lxc.aa_allow_incomplete' => 1,
227 'lxc.se_context' => 1,
228 'lxc.seccomp' => 1,
229 'lxc.id_map' => 1,
230 'lxc.hook.pre-start' => 1,
231 'lxc.hook.pre-mount' => 1,
232 'lxc.hook.mount' => 1,
233 'lxc.hook.start' => 1,
234 'lxc.hook.post-stop' => 1,
235 'lxc.hook.clone' => 1,
236 'lxc.hook.destroy' => 1,
237 'lxc.loglevel' => 1,
238 'lxc.logfile' => 1,
239 'lxc.start.auto' => 1,
240 'lxc.start.delay' => 1,
241 'lxc.start.order' => 1,
242 'lxc.group' => 1,
243 'lxc.environment' => 1,
244 'lxc.' => 1,
245 'lxc.' => 1,
246 'lxc.' => 1,
247 'lxc.' => 1,
248};
249
769fbfab
WB
250my $netconf_desc = {
251 type => {
252 type => 'string',
253 optional => 1,
254 description => "Network interface type.",
255 enum => [qw(veth)],
256 },
257 name => {
258 type => 'string',
259 format_description => 'String',
260 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
261 pattern => '[-_.\w\d]+',
262 },
263 bridge => {
264 type => 'string',
265 format_description => 'vmbr<Number>',
266 description => 'Bridge to attach the network device to.',
267 pattern => '[-_.\w\d]+',
268 },
269 hwaddr => {
270 type => 'string',
271 format_description => 'MAC',
272 description => 'Bridge to attach the network device to. (lxc.network.hwaddr)',
273 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
274 optional => 1,
275 },
276 mtu => {
277 type => 'integer',
278 format_description => 'Number',
279 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
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
22a77285
DM
808 $d->{uptime} = 100; # fixme:
809
238a56cb
DM
810 $d->{mem} = read_cgroup_value('memory', $vmid, 'memory.usage_in_bytes');
811 $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
b5289322
AD
812
813 my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
1e647c7c 814 my @bytes = split(/\n/, $blkio_bytes);
b5289322 815 foreach my $byte (@bytes) {
1e647c7c
DM
816 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
817 $d->{diskread} = $2 if $key eq 'Read';
818 $d->{diskwrite} = $2 if $key eq 'Write';
819 }
b5289322 820 }
238a56cb 821 }
cbb03fea 822
f76a2828
DM
823 return $list;
824}
825
27916659
DM
826my $parse_size = sub {
827 my ($value) = @_;
828
829 return undef if $value !~ m/^(\d+(\.\d+)?)([KMG])?$/;
830 my ($size, $unit) = ($1, $3);
831 if ($unit) {
832 if ($unit eq 'K') {
833 $size = $size * 1024;
834 } elsif ($unit eq 'M') {
835 $size = $size * 1024 * 1024;
836 } elsif ($unit eq 'G') {
837 $size = $size * 1024 * 1024 * 1024;
838 }
839 }
840 return int($size);
841};
842
8ed5ff9d
DM
843my $format_size = sub {
844 my ($size) = @_;
845
846 $size = int($size);
847
848 my $kb = int($size/1024);
849 return $size if $kb*1024 != $size;
850
851 my $mb = int($kb/1024);
852 return "${kb}K" if $mb*1024 != $kb;
853
854 my $gb = int($mb/1024);
855 return "${mb}M" if $gb*1024 != $mb;
856
857 return "${gb}G";
858};
859
27916659
DM
860sub parse_ct_mountpoint {
861 my ($data) = @_;
862
863 $data //= '';
864
1b2c1e8c
WB
865 my $res;
866 eval { $res = PVE::JSONSchema::parse_property_string($mp_desc, $data) };
867 if ($@) {
868 warn $@;
869 return undef;
27916659
DM
870 }
871
d33329c2 872 return undef if !defined($res->{volume});
27916659 873
27916659
DM
874 if ($res->{size}) {
875 return undef if !defined($res->{size} = &$parse_size($res->{size}));
876 }
877
878 return $res;
879}
7dfc49cc 880
dde7b02b 881sub print_ct_mountpoint {
4fee75fd 882 my ($info, $nomp) = @_;
bb1ac2de
DM
883
884 my $opts = '';
885
886 die "missing volume\n" if !$info->{volume};
887
8ed5ff9d 888 foreach my $o (qw(backup)) {
7092c9f1 889 $opts .= ",$o=$info->{$o}" if defined($info->{$o});
bb1ac2de 890 }
8ed5ff9d
DM
891
892 if ($info->{size}) {
893 $opts .= ",size=" . &$format_size($info->{size});
894 }
895
4fee75fd 896 $opts .= ",mp=$info->{mp}" if !$nomp;
bb1ac2de
DM
897
898 return "$info->{volume}$opts";
899}
900
7dfc49cc 901sub print_lxc_network {
f76a2828
DM
902 my $net = shift;
903
bedeaaf1 904 die "no network name defined\n" if !$net->{name};
f76a2828 905
bedeaaf1 906 my $res = "name=$net->{name}";
7dfc49cc 907
bedeaaf1 908 foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
f76a2828
DM
909 next if !defined($net->{$k});
910 $res .= ",$k=$net->{$k}";
911 }
7dfc49cc 912
f76a2828
DM
913 return $res;
914}
915
7dfc49cc
DM
916sub parse_lxc_network {
917 my ($data) = @_;
918
919 my $res = {};
920
921 return $res if !$data;
922
1b2c1e8c
WB
923 eval { $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data) };
924 if ($@) {
925 warn $@;
926 return undef;
7dfc49cc
DM
927 }
928
929 $res->{type} = 'veth';
93cdbbfb 930 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 931
7dfc49cc
DM
932 return $res;
933}
f76a2828 934
238a56cb
DM
935sub read_cgroup_value {
936 my ($group, $vmid, $name, $full) = @_;
937
938 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
939
940 return PVE::Tools::file_get_contents($path) if $full;
941
942 return PVE::Tools::file_read_firstline($path);
943}
944
bf0b8c43
AD
945sub write_cgroup_value {
946 my ($group, $vmid, $name, $value) = @_;
947
948 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
949 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
950
951}
952
52f1d76b
DM
953sub find_lxc_console_pids {
954
955 my $res = {};
956
957 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
958 my ($pid) = @_;
959
960 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
961 return if !$cmdline;
962
963 my @args = split(/\0/, $cmdline);
964
965 # serach for lxc-console -n <vmid>
cbb03fea 966 return if scalar(@args) != 3;
52f1d76b
DM
967 return if $args[1] ne '-n';
968 return if $args[2] !~ m/^\d+$/;
969 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 970
52f1d76b 971 my $vmid = $args[2];
cbb03fea 972
52f1d76b
DM
973 push @{$res->{$vmid}}, $pid;
974 });
975
976 return $res;
977}
978
bedeaaf1
AD
979sub find_lxc_pid {
980 my ($vmid) = @_;
981
982 my $pid = undef;
983 my $parser = sub {
984 my $line = shift;
8b25977f 985 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1
AD
986 };
987 PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
988
8b25977f 989 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 990
8b25977f 991 return $pid;
bedeaaf1
AD
992}
993
55fa4e09
DM
994my $ipv4_reverse_mask = [
995 '0.0.0.0',
996 '128.0.0.0',
997 '192.0.0.0',
998 '224.0.0.0',
999 '240.0.0.0',
1000 '248.0.0.0',
1001 '252.0.0.0',
1002 '254.0.0.0',
1003 '255.0.0.0',
1004 '255.128.0.0',
1005 '255.192.0.0',
1006 '255.224.0.0',
1007 '255.240.0.0',
1008 '255.248.0.0',
1009 '255.252.0.0',
1010 '255.254.0.0',
1011 '255.255.0.0',
1012 '255.255.128.0',
1013 '255.255.192.0',
1014 '255.255.224.0',
1015 '255.255.240.0',
1016 '255.255.248.0',
1017 '255.255.252.0',
1018 '255.255.254.0',
1019 '255.255.255.0',
1020 '255.255.255.128',
1021 '255.255.255.192',
1022 '255.255.255.224',
1023 '255.255.255.240',
1024 '255.255.255.248',
1025 '255.255.255.252',
1026 '255.255.255.254',
1027 '255.255.255.255',
1028];
cbb03fea
DM
1029
1030# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
1031# CIDR networks
1032sub parse_ipv4_cidr {
1033 my ($cidr, $noerr) = @_;
1034
1035 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 < 32)) {
1036 return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
1037 }
cbb03fea 1038
55fa4e09 1039 return undef if $noerr;
cbb03fea 1040
55fa4e09
DM
1041 die "unable to parse ipv4 address/mask\n";
1042}
93285df8 1043
a12a36e0
WL
1044sub check_lock {
1045 my ($conf) = @_;
1046
27916659 1047 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
a12a36e0
WL
1048}
1049
e22af68f
AG
1050sub check_protection {
1051 my ($vm_conf, $err_msg) = @_;
1052
1053 if ($vm_conf->{protection}) {
1054 die "$err_msg - protection mode enabled\n";
1055 }
1056}
1057
27916659 1058sub update_lxc_config {
c628ffa1 1059 my ($storage_cfg, $vmid, $conf) = @_;
b80dd50a 1060
bb1ac2de
DM
1061 my $dir = "/var/lib/lxc/$vmid";
1062
1063 if ($conf->{template}) {
1064
1065 unlink "$dir/config";
1066
1067 return;
1068 }
1069
27916659 1070 my $raw = '';
b80dd50a 1071
27916659
DM
1072 die "missing 'arch' - internal error" if !$conf->{arch};
1073 $raw .= "lxc.arch = $conf->{arch}\n";
b80dd50a 1074
27916659 1075 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
c1d32b55 1076 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
27916659
DM
1077 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1078 } else {
1079 die "implement me";
1080 }
b80dd50a 1081
6f035afe 1082 if (!has_dev_console($conf)) {
eeaea429
DM
1083 $raw .= "lxc.console = none\n";
1084 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1085 }
4f958489 1086
0d0ca400 1087 my $ttycount = get_tty_count($conf);
27916659 1088 $raw .= "lxc.tty = $ttycount\n";
cbb03fea 1089
a691a5a3
DM
1090 # some init scripts expects a linux terminal (turnkey).
1091 $raw .= "lxc.environment = TERM=linux\n";
1092
27916659
DM
1093 my $utsname = $conf->{hostname} || "CT$vmid";
1094 $raw .= "lxc.utsname = $utsname\n";
cbb03fea 1095
27916659
DM
1096 my $memory = $conf->{memory} || 512;
1097 my $swap = $conf->{swap} // 0;
1098
1099 my $lxcmem = int($memory*1024*1024);
1100 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
a12a36e0 1101
27916659
DM
1102 my $lxcswap = int(($memory + $swap)*1024*1024);
1103 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1104
1105 if (my $cpulimit = $conf->{cpulimit}) {
1106 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1107 my $value = int(100000*$cpulimit);
1108 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
a12a36e0
WL
1109 }
1110
27916659
DM
1111 my $shares = $conf->{cpuunits} || 1024;
1112 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1113
21be9680 1114 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
b15c75fc 1115 $mountpoint->{mp} = '/';
b15c75fc 1116
30de33be
DM
1117 my ($path, $use_loopdev) = mountpoint_mount_path($mountpoint, $storage_cfg);
1118 $path = "loop:$path" if $use_loopdev;
a3076d81 1119
21be9680 1120 $raw .= "lxc.rootfs = $path\n";
27916659
DM
1121
1122 my $netcount = 0;
1123 foreach my $k (keys %$conf) {
1124 next if $k !~ m/^net(\d+)$/;
1125 my $ind = $1;
a16d94c8 1126 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1127 $netcount++;
1128 $raw .= "lxc.network.type = veth\n";
18862537 1129 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1130 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1131 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1132 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1133 }
1134
e576f689
DM
1135 if (my $lxcconf = $conf->{lxc}) {
1136 foreach my $entry (@$lxcconf) {
1137 my ($k, $v) = @$entry;
1138 $netcount++ if $k eq 'lxc.network.type';
1139 $raw .= "$k = $v\n";
1140 }
1141 }
27916659 1142
e576f689
DM
1143 $raw .= "lxc.network.type = empty\n" if !$netcount;
1144
27916659
DM
1145 File::Path::mkpath("$dir/rootfs");
1146
1147 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1148}
1149
117636e5
DM
1150# verify and cleanup nameserver list (replace \0 with ' ')
1151sub verify_nameserver_list {
1152 my ($nameserver_list) = @_;
1153
1154 my @list = ();
1155 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1156 PVE::JSONSchema::pve_verify_ip($server);
1157 push @list, $server;
1158 }
1159
1160 return join(' ', @list);
1161}
1162
1163sub verify_searchdomain_list {
1164 my ($searchdomain_list) = @_;
1165
1166 my @list = ();
1167 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1168 # todo: should we add checks for valid dns domains?
1169 push @list, $server;
1170 }
1171
1172 return join(' ', @list);
1173}
1174
27916659 1175sub update_pct_config {
93285df8
DM
1176 my ($vmid, $conf, $running, $param, $delete) = @_;
1177
bf0b8c43
AD
1178 my @nohotplug;
1179
7b49dfe0 1180 my $new_disks = 0;
4fee75fd 1181
cbb03fea
DM
1182 my $rootdir;
1183 if ($running) {
bedeaaf1 1184 my $pid = find_lxc_pid($vmid);
cbb03fea 1185 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1186 }
1187
93285df8
DM
1188 if (defined($delete)) {
1189 foreach my $opt (@$delete) {
27916659 1190 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1191 die "unable to delete required option '$opt'\n";
1192 } elsif ($opt eq 'swap') {
27916659 1193 delete $conf->{$opt};
bf0b8c43 1194 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1195 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1196 delete $conf->{$opt};
4f958489 1197 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1198 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
27916659 1199 delete $conf->{$opt};
bf0b8c43
AD
1200 push @nohotplug, $opt;
1201 next if $running;
68fba17b 1202 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1203 delete $conf->{$opt};
68fba17b
AD
1204 next if !$running;
1205 my $netid = $1;
18862537 1206 PVE::Network::veth_delete("veth${vmid}i$netid");
7e806596
AG
1207 } elsif ($opt eq 'protection') {
1208 delete $conf->{$opt};
4fee75fd 1209 } elsif ($opt =~ m/^mp(\d+)$/) {
e22af68f 1210 check_protection($conf, "can't remove CT $vmid drive '$opt'");
4fee75fd
WB
1211 delete $conf->{$opt};
1212 push @nohotplug, $opt;
1213 next if $running;
93285df8
DM
1214 } else {
1215 die "implement me"
1216 }
706c9791 1217 write_config($vmid, $conf) if $running;
93285df8
DM
1218 }
1219 }
1220
be6383d7
WB
1221 # There's no separate swap size to configure, there's memory and "total"
1222 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1223 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1224 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1225 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1226
1227 $wanted_memory //= ($conf->{memory} || 512);
1228 $wanted_swap //= ($conf->{swap} || 0);
1229
1230 my $total = $wanted_memory + $wanted_swap;
1231 if ($running) {
1232 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1233 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1234 }
27916659
DM
1235 $conf->{memory} = $wanted_memory;
1236 $conf->{swap} = $wanted_swap;
1237
706c9791 1238 write_config($vmid, $conf) if $running;
be6383d7
WB
1239 }
1240
93285df8
DM
1241 foreach my $opt (keys %$param) {
1242 my $value = $param->{$opt};
1243 if ($opt eq 'hostname') {
27916659 1244 $conf->{$opt} = $value;
a99b3509 1245 } elsif ($opt eq 'onboot') {
27916659 1246 $conf->{$opt} = $value ? 1 : 0;
a3249355 1247 } elsif ($opt eq 'startup') {
27916659 1248 $conf->{$opt} = $value;
40603eb3 1249 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
e576f689
DM
1250 $conf->{$opt} = $value;
1251 push @nohotplug, $opt;
1252 next if $running;
ffa1d001 1253 } elsif ($opt eq 'nameserver') {
117636e5 1254 my $list = verify_nameserver_list($value);
27916659 1255 $conf->{$opt} = $list;
bf0b8c43
AD
1256 push @nohotplug, $opt;
1257 next if $running;
ffa1d001 1258 } elsif ($opt eq 'searchdomain') {
117636e5 1259 my $list = verify_searchdomain_list($value);
27916659 1260 $conf->{$opt} = $list;
bf0b8c43
AD
1261 push @nohotplug, $opt;
1262 next if $running;
45573f7c 1263 } elsif ($opt eq 'cpulimit') {
27916659
DM
1264 $conf->{$opt} = $value;
1265 push @nohotplug, $opt; # fixme: hotplug
1266 next;
b80dd50a 1267 } elsif ($opt eq 'cpuunits') {
27916659 1268 $conf->{$opt} = $value;
bf0b8c43 1269 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1270 } elsif ($opt eq 'description') {
27916659 1271 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1272 } elsif ($opt =~ m/^net(\d+)$/) {
1273 my $netid = $1;
a16d94c8 1274 my $net = parse_lxc_network($value);
27916659
DM
1275 if (!$running) {
1276 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1277 } else {
bedeaaf1
AD
1278 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1279 }
7e806596
AG
1280 } elsif ($opt eq 'protection') {
1281 $conf->{$opt} = $value ? 1 : 0;
4fee75fd 1282 } elsif ($opt =~ m/^mp(\d+)$/) {
e22af68f 1283 check_protection($conf, "can't update CT $vmid drive '$opt'");
4fee75fd 1284 $conf->{$opt} = $value;
7b49dfe0 1285 $new_disks = 1;
4fee75fd
WB
1286 push @nohotplug, $opt;
1287 next;
1288 } elsif ($opt eq 'rootfs') {
e22af68f 1289 check_protection($conf, "can't update CT $vmid drive '$opt'");
b51a98d4 1290 die "implement me: $opt";
93285df8 1291 } else {
a92f66c9 1292 die "implement me: $opt";
93285df8 1293 }
706c9791 1294 write_config($vmid, $conf) if $running;
93285df8 1295 }
bf0b8c43 1296
5cfa0567
DM
1297 if ($running && scalar(@nohotplug)) {
1298 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1299 }
4fee75fd 1300
7b49dfe0 1301 if ($new_disks) {
4fee75fd 1302 my $storage_cfg = PVE::Storage::config();
6c871c36 1303 create_disks($storage_cfg, $vmid, $conf, $conf);
4fee75fd 1304 }
93285df8 1305}
c325b32f 1306
6f035afe
DM
1307sub has_dev_console {
1308 my ($conf) = @_;
1309
1310 return !(defined($conf->{console}) && !$conf->{console});
1311}
1312
0d0ca400
DM
1313sub get_tty_count {
1314 my ($conf) = @_;
1315
1316 return $conf->{tty} // $confdesc->{tty}->{default};
1317}
1318
aca816ad
DM
1319sub get_cmode {
1320 my ($conf) = @_;
1321
1322 return $conf->{cmode} // $confdesc->{cmode}->{default};
1323}
1324
1325sub get_console_command {
1326 my ($vmid, $conf) = @_;
1327
1328 my $cmode = get_cmode($conf);
1329
1330 if ($cmode eq 'console') {
1331 return ['lxc-console', '-n', $vmid, '-t', 0];
1332 } elsif ($cmode eq 'tty') {
1333 return ['lxc-console', '-n', $vmid];
1334 } elsif ($cmode eq 'shell') {
1335 return ['lxc-attach', '--clear-env', '-n', $vmid];
1336 } else {
1337 die "internal error";
1338 }
1339}
1340
c325b32f
DM
1341sub get_primary_ips {
1342 my ($conf) = @_;
1343
1344 # return data from net0
cbb03fea 1345
27916659 1346 return undef if !defined($conf->{net0});
a16d94c8 1347 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1348
1349 my $ipv4 = $net->{ip};
db78a181
WB
1350 if ($ipv4) {
1351 if ($ipv4 =~ /^(dhcp|manual)$/) {
1352 $ipv4 = undef
1353 } else {
1354 $ipv4 =~ s!/\d+$!!;
1355 }
1356 }
65e5eaa3 1357 my $ipv6 = $net->{ip6};
db78a181
WB
1358 if ($ipv6) {
1359 if ($ipv6 =~ /^(dhcp|manual)$/) {
1360 $ipv6 = undef;
1361 } else {
1362 $ipv6 =~ s!/\d+$!!;
1363 }
1364 }
cbb03fea 1365
c325b32f
DM
1366 return ($ipv4, $ipv6);
1367}
148d1cb4 1368
ef241384 1369
27916659 1370sub destroy_lxc_container {
148d1cb4
DM
1371 my ($storage_cfg, $vmid, $conf) = @_;
1372
db8989e1
WB
1373 foreach_mountpoint($conf, sub {
1374 my ($ms, $mountpoint) = @_;
96225eea
EK
1375
1376 # skip bind mounts and block devices
1377 if ($mountpoint->{volume} =~ m|^/|) {
1378 return;
1379 }
1380
db8989e1
WB
1381 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $mountpoint->{volume});
1382 PVE::Storage::vdisk_free($storage_cfg, $mountpoint->{volume}) if $vmid == $owner;
1383 });
1384
27916659
DM
1385 rmdir "/var/lib/lxc/$vmid/rootfs";
1386 unlink "/var/lib/lxc/$vmid/config";
1387 rmdir "/var/lib/lxc/$vmid";
1388 destroy_config($vmid);
1389
1390 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1391 #PVE::Tools::run_command($cmd);
148d1cb4 1392}
68fba17b 1393
ef241384 1394sub vm_stop_cleanup {
5fa890f0 1395 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
ef241384
DM
1396
1397 eval {
1398 if (!$keepActive) {
bf9d912c 1399
09aa32fd 1400 my $vollist = get_vm_volumes($conf);
a8b6b8a7 1401 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1402 }
1403 };
1404 warn $@ if $@; # avoid errors - just warn
1405}
1406
93cdbbfb
AD
1407my $safe_num_ne = sub {
1408 my ($a, $b) = @_;
1409
1410 return 0 if !defined($a) && !defined($b);
1411 return 1 if !defined($a);
1412 return 1 if !defined($b);
1413
1414 return $a != $b;
1415};
1416
1417my $safe_string_ne = sub {
1418 my ($a, $b) = @_;
1419
1420 return 0 if !defined($a) && !defined($b);
1421 return 1 if !defined($a);
1422 return 1 if !defined($b);
1423
1424 return $a ne $b;
1425};
1426
1427sub update_net {
bedeaaf1 1428 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1429
18862537
WB
1430 if ($newnet->{type} ne 'veth') {
1431 # for when there are physical interfaces
1432 die "cannot update interface of type $newnet->{type}";
1433 }
1434
1435 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1436 my $eth = $newnet->{name};
1437
18862537
WB
1438 if (my $oldnetcfg = $conf->{$opt}) {
1439 my $oldnet = parse_lxc_network($oldnetcfg);
1440
1441 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1442 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1443
18862537 1444 PVE::Network::veth_delete($veth);
bedeaaf1 1445 delete $conf->{$opt};
706c9791 1446 write_config($vmid, $conf);
93cdbbfb 1447
18862537 1448 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1449
18862537
WB
1450 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1451 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1452 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1453
18862537 1454 if ($oldnet->{bridge}) {
bedeaaf1 1455 PVE::Network::tap_unplug($veth);
18862537
WB
1456 foreach (qw(bridge tag firewall)) {
1457 delete $oldnet->{$_};
1458 }
1459 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1460 write_config($vmid, $conf);
bedeaaf1 1461 }
93cdbbfb 1462
18862537
WB
1463 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1464 foreach (qw(bridge tag firewall)) {
1465 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1466 }
1467 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1468 write_config($vmid, $conf);
93cdbbfb
AD
1469 }
1470 } else {
18862537 1471 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1472 }
1473
bedeaaf1 1474 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1475}
1476
1477sub hotplug_net {
18862537 1478 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1479
18862537 1480 my $veth = "veth${vmid}i${netid}";
cbb03fea 1481 my $vethpeer = $veth . "p";
93cdbbfb
AD
1482 my $eth = $newnet->{name};
1483
1484 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1485 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1486
cbb03fea 1487 # attach peer in container
93cdbbfb
AD
1488 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1489 PVE::Tools::run_command($cmd);
1490
cbb03fea 1491 # link up peer in container
93cdbbfb
AD
1492 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1493 PVE::Tools::run_command($cmd);
bedeaaf1 1494
18862537
WB
1495 my $done = { type => 'veth' };
1496 foreach (qw(bridge tag firewall hwaddr name)) {
1497 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1498 }
1499 $conf->{$opt} = print_lxc_network($done);
bedeaaf1 1500
706c9791 1501 write_config($vmid, $conf);
93cdbbfb
AD
1502}
1503
68a05bb3 1504sub update_ipconfig {
bedeaaf1
AD
1505 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1506
f2104b80 1507 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
bedeaaf1 1508
18862537 1509 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1510 my $deleted = [];
1511 my $added = [];
8d723477
WB
1512 my $nscmd = sub {
1513 my $cmdargs = shift;
1514 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1515 };
8d723477 1516 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1517
84e0c123 1518 my $change_ip_config = sub {
f39002a6
DM
1519 my ($ipversion) = @_;
1520
1521 my $family_opt = "-$ipversion";
1522 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1523 my $gw= "gw$suffix";
1524 my $ip= "ip$suffix";
bedeaaf1 1525
6178b0dd
WB
1526 my $newip = $newnet->{$ip};
1527 my $newgw = $newnet->{$gw};
1528 my $oldip = $optdata->{$ip};
1529
1530 my $change_ip = &$safe_string_ne($oldip, $newip);
1531 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1532
84e0c123 1533 return if !$change_ip && !$change_gw;
68a05bb3 1534
84e0c123 1535 # step 1: add new IP, if this fails we cancel
6178b0dd 1536 if ($change_ip && $newip && $newip !~ /^(?:auto|dhcp)$/) {
8d723477 1537 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1538 if (my $err = $@) {
1539 warn $err;
1540 return;
1541 }
bedeaaf1 1542 }
bedeaaf1 1543
84e0c123
WB
1544 # step 2: replace gateway
1545 # If this fails we delete the added IP and cancel.
1546 # If it succeeds we save the config and delete the old IP, ignoring
1547 # errors. The config is then saved.
1548 # Note: 'ip route replace' can add
1549 if ($change_gw) {
6178b0dd 1550 if ($newgw) {
8d723477 1551 eval { &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw); };
84e0c123
WB
1552 if (my $err = $@) {
1553 warn $err;
1554 # the route was not replaced, the old IP is still available
1555 # rollback (delete new IP) and cancel
1556 if ($change_ip) {
8d723477 1557 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1558 warn $@ if $@; # no need to die here
1559 }
1560 return;
1561 }
1562 } else {
8d723477 1563 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1564 # if the route was not deleted, the guest might have deleted it manually
1565 # warn and continue
1566 warn $@ if $@;
1567 }
2bfd1615 1568 }
2bfd1615 1569
6178b0dd 1570 # from this point on we save the configuration
84e0c123 1571 # step 3: delete old IP ignoring errors
6178b0dd 1572 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1573 # We need to enable promote_secondaries, otherwise our newly added
1574 # address will be removed along with the old one.
1575 my $promote = 0;
1576 eval {
1577 if ($ipversion == 4) {
1578 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1579 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1580 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1581 }
1582 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1583 };
84e0c123 1584 warn $@ if $@; # no need to die here
8d723477
WB
1585
1586 if ($ipversion == 4) {
1587 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1588 }
bedeaaf1
AD
1589 }
1590
84e0c123
WB
1591 foreach my $property ($ip, $gw) {
1592 if ($newnet->{$property}) {
1593 $optdata->{$property} = $newnet->{$property};
1594 } else {
1595 delete $optdata->{$property};
1596 }
bedeaaf1 1597 }
18862537 1598 $conf->{$opt} = print_lxc_network($optdata);
706c9791 1599 write_config($vmid, $conf);
84e0c123
WB
1600 $lxc_setup->setup_network($conf);
1601 };
bedeaaf1 1602
f39002a6
DM
1603 &$change_ip_config(4);
1604 &$change_ip_config(6);
489e960d
WL
1605
1606}
1607
a92f66c9
WL
1608# Internal snapshots
1609
1610# NOTE: Snapshot create/delete involves several non-atomic
1611# action, and can take a long time.
1612# So we try to avoid locking the file and use 'lock' variable
1613# inside the config file instead.
1614
1615my $snapshot_copy_config = sub {
1616 my ($source, $dest) = @_;
1617
1618 foreach my $k (keys %$source) {
1619 next if $k eq 'snapshots';
09d3ec42
DM
1620 next if $k eq 'snapstate';
1621 next if $k eq 'snaptime';
1622 next if $k eq 'vmstate';
1623 next if $k eq 'lock';
a92f66c9 1624 next if $k eq 'digest';
09d3ec42 1625 next if $k eq 'description';
a92f66c9
WL
1626
1627 $dest->{$k} = $source->{$k};
1628 }
1629};
1630
1631my $snapshot_prepare = sub {
1632 my ($vmid, $snapname, $comment) = @_;
1633
1634 my $snap;
1635
1636 my $updatefn = sub {
1637
1638 my $conf = load_config($vmid);
1639
bb1ac2de
DM
1640 die "you can't take a snapshot if it's a template\n"
1641 if is_template($conf);
1642
a92f66c9
WL
1643 check_lock($conf);
1644
09d3ec42 1645 $conf->{lock} = 'snapshot';
a92f66c9
WL
1646
1647 die "snapshot name '$snapname' already used\n"
1648 if defined($conf->{snapshots}->{$snapname});
1649
1650 my $storecfg = PVE::Storage::config();
1651 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1652
1653 $snap = $conf->{snapshots}->{$snapname} = {};
1654
1655 &$snapshot_copy_config($conf, $snap);
1656
09d3ec42
DM
1657 $snap->{'snapstate'} = "prepare";
1658 $snap->{'snaptime'} = time();
1659 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1660 $conf->{snapshots}->{$snapname} = $snap;
1661
706c9791 1662 write_config($vmid, $conf);
a92f66c9
WL
1663 };
1664
1665 lock_container($vmid, 10, $updatefn);
1666
1667 return $snap;
1668};
1669
1670my $snapshot_commit = sub {
1671 my ($vmid, $snapname) = @_;
1672
1673 my $updatefn = sub {
1674
1675 my $conf = load_config($vmid);
1676
1677 die "missing snapshot lock\n"
09d3ec42 1678 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1679
27916659 1680 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1681 if !defined($conf->{snapshots}->{$snapname});
1682
1683 die "wrong snapshot state\n"
09d3ec42
DM
1684 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1685 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1686
09d3ec42
DM
1687 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1688 delete $conf->{lock};
1689 $conf->{parent} = $snapname;
a92f66c9 1690
706c9791 1691 write_config($vmid, $conf);
a92f66c9
WL
1692 };
1693
1694 lock_container($vmid, 10 ,$updatefn);
1695};
1696
1697sub has_feature {
1698 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1699
a92f66c9 1700 my $err;
09d3ec42 1701
8bf50651
DM
1702 foreach_mountpoint($conf, sub {
1703 my ($ms, $mountpoint) = @_;
1704
2c3ed8c4
DM
1705 return if $err; # skip further test
1706
8bf50651
DM
1707 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1708
1709 # TODO: implement support for mountpoints
1710 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1711 if $ms ne 'rootfs';
1712 });
a92f66c9
WL
1713
1714 return $err ? 0 : 1;
1715}
1716
489e960d
WL
1717sub snapshot_create {
1718 my ($vmid, $snapname, $comment) = @_;
1719
a92f66c9
WL
1720 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1721
09d3ec42 1722 my $conf = load_config($vmid);
a92f66c9 1723
a92f66c9
WL
1724 my $running = check_running($vmid);
1725 eval {
1726 if ($running) {
4db769cf
WB
1727 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
1728 PVE::Tools::run_command(['/bin/sync']);
a92f66c9
WL
1729 };
1730
1731 my $storecfg = PVE::Storage::config();
706c9791 1732 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
09d3ec42 1733 my $volid = $rootinfo->{volume};
a92f66c9 1734
a92f66c9 1735 if ($running) {
4db769cf 1736 PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
a92f66c9 1737 };
489e960d 1738
a92f66c9
WL
1739 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1740 &$snapshot_commit($vmid, $snapname);
1741 };
1742 if(my $err = $@) {
31429832 1743 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1744 die "$err\n";
1745 }
68a05bb3
AD
1746}
1747
57ccb3f8
WL
1748sub snapshot_delete {
1749 my ($vmid, $snapname, $force) = @_;
1750
31429832
WL
1751 my $snap;
1752
1753 my $conf;
1754
1755 my $updatefn = sub {
1756
1757 $conf = load_config($vmid);
1758
bb1ac2de
DM
1759 die "you can't delete a snapshot if vm is a template\n"
1760 if is_template($conf);
1761
31429832
WL
1762 $snap = $conf->{snapshots}->{$snapname};
1763
1764 check_lock($conf);
1765
1766 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1767
09d3ec42 1768 $snap->{snapstate} = 'delete';
31429832 1769
706c9791 1770 write_config($vmid, $conf);
31429832
WL
1771 };
1772
1773 lock_container($vmid, 10, $updatefn);
1774
1775 my $storecfg = PVE::Storage::config();
1776
1777 my $del_snap = sub {
1778
1779 check_lock($conf);
1780
09d3ec42
DM
1781 if ($conf->{parent} eq $snapname) {
1782 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1783 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1784 } else {
09d3ec42 1785 delete $conf->{parent};
31429832
WL
1786 }
1787 }
1788
1789 delete $conf->{snapshots}->{$snapname};
1790
706c9791 1791 write_config($vmid, $conf);
31429832
WL
1792 };
1793
09d3ec42 1794 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
706c9791 1795 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42 1796 my $volid = $rootinfo->{volume};
31429832
WL
1797
1798 eval {
1799 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1800 };
1801 my $err = $@;
1802
1803 if(!$err || ($err && $force)) {
1804 lock_container($vmid, 10, $del_snap);
1805 if ($err) {
1806 die "Can't delete snapshot: $vmid $snapname $err\n";
1807 }
1808 }
57ccb3f8
WL
1809}
1810
723157f6
WL
1811sub snapshot_rollback {
1812 my ($vmid, $snapname) = @_;
1813
6860ba0c
WL
1814 my $storecfg = PVE::Storage::config();
1815
1816 my $conf = load_config($vmid);
1817
bb1ac2de
DM
1818 die "you can't rollback if vm is a template\n" if is_template($conf);
1819
6860ba0c
WL
1820 my $snap = $conf->{snapshots}->{$snapname};
1821
1822 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1823
09d3ec42 1824 my $rootfs = $snap->{rootfs};
706c9791 1825 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42
DM
1826 my $volid = $rootinfo->{volume};
1827
1828 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1829
1830 my $updatefn = sub {
1831
09d3ec42
DM
1832 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1833 if $snap->{snapstate};
6860ba0c
WL
1834
1835 check_lock($conf);
6860ba0c 1836
b935932a 1837 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1838
1839 die "unable to rollback vm $vmid: vm is running\n"
1840 if check_running($vmid);
1841
09d3ec42 1842 $conf->{lock} = 'rollback';
6860ba0c
WL
1843
1844 my $forcemachine;
1845
1846 # copy snapshot config to current config
1847
1848 my $tmp_conf = $conf;
1849 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1850 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1851 delete $conf->{snaptime};
1852 delete $conf->{snapname};
1853 $conf->{parent} = $snapname;
6860ba0c 1854
706c9791 1855 write_config($vmid, $conf);
6860ba0c
WL
1856 };
1857
1858 my $unlockfn = sub {
09d3ec42 1859 delete $conf->{lock};
706c9791 1860 write_config($vmid, $conf);
6860ba0c
WL
1861 };
1862
1863 lock_container($vmid, 10, $updatefn);
1864
09d3ec42 1865 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1866
1867 lock_container($vmid, 5, $unlockfn);
723157f6 1868}
b935932a 1869
bb1ac2de
DM
1870sub template_create {
1871 my ($vmid, $conf) = @_;
1872
1873 my $storecfg = PVE::Storage::config();
1874
706c9791 1875 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
bb1ac2de
DM
1876 my $volid = $rootinfo->{volume};
1877
1878 die "Template feature is not available for '$volid'\n"
1879 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1880
1881 PVE::Storage::activate_volumes($storecfg, [$volid]);
1882
1883 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1884 $rootinfo->{volume} = $template_volid;
4fee75fd 1885 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
bb1ac2de
DM
1886
1887 write_config($vmid, $conf);
1888}
1889
1890sub is_template {
1891 my ($conf) = @_;
1892
1893 return 1 if defined $conf->{template} && $conf->{template} == 1;
1894}
1895
9622e848
DM
1896sub mountpoint_names {
1897 my ($reverse) = @_;
ced7fddb 1898
9622e848 1899 my @names = ('rootfs');
eaebef36
DM
1900
1901 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
9622e848
DM
1902 push @names, "mp$i";
1903 }
1904
1905 return $reverse ? reverse @names : @names;
1906}
1907
3c9dbfa9
WB
1908# The container might have *different* symlinks than the host. realpath/abs_path
1909# use the actual filesystem to resolve links.
1910sub sanitize_mountpoint {
1911 my ($mp) = @_;
1912 $mp = '/' . $mp; # we always start with a slash
1913 $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
1914 $mp =~ s@/\./@@g; # collapse /./
1915 $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
1916 $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1917 $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1918 return $mp;
1919}
1920
9622e848
DM
1921sub foreach_mountpoint_full {
1922 my ($conf, $reverse, $func) = @_;
1923
1924 foreach my $key (mountpoint_names($reverse)) {
1925 my $value = $conf->{$key};
1926 next if !defined($value);
1927 my $mountpoint = parse_ct_mountpoint($value);
3c9dbfa9
WB
1928
1929 # just to be sure: rootfs is /
1930 my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
1931 $mountpoint->{mp} = sanitize_mountpoint($path);
1932
1933 $path = $mountpoint->{volume};
1934 $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
1935
eaebef36 1936 &$func($key, $mountpoint);
ced7fddb
AD
1937 }
1938}
1939
9622e848
DM
1940sub foreach_mountpoint {
1941 my ($conf, $func) = @_;
1942
1943 foreach_mountpoint_full($conf, 0, $func);
1944}
1945
1946sub foreach_mountpoint_reverse {
1947 my ($conf, $func) = @_;
1948
1949 foreach_mountpoint_full($conf, 1, $func);
1950}
1951
52389a07
DM
1952sub check_ct_modify_config_perm {
1953 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1954
1955 return 1 if $authuser ne 'root@pam';
1956
1957 foreach my $opt (@$key_list) {
1958
1959 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1960 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
e59a61ed 1961 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
52389a07
DM
1962 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1963 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1964 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1965 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1966 $opt eq 'searchdomain' || $opt eq 'hostname') {
1967 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1968 } else {
1969 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
1970 }
1971 }
1972
1973 return 1;
1974}
1975
9622e848 1976sub umount_all {
da629848 1977 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
9622e848
DM
1978
1979 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1980 my $volid_list = get_vm_volumes($conf);
1981
1982 foreach_mountpoint_reverse($conf, sub {
1983 my ($ms, $mountpoint) = @_;
1984
1985 my $volid = $mountpoint->{volume};
1986 my $mount = $mountpoint->{mp};
1987
1988 return if !$volid || !$mount;
1989
d18f96b4 1990 my $mount_path = "$rootdir/$mount";
f845a93d 1991 $mount_path =~ s!/+!/!g;
9622e848 1992
228a5a1d
WL
1993 return if !PVE::ProcFSTools::is_mounted($mount_path);
1994
9622e848 1995 eval {
d18f96b4 1996 PVE::Tools::run_command(['umount', '-d', $mount_path]);
9622e848
DM
1997 };
1998 if (my $err = $@) {
1999 if ($noerr) {
2000 warn $err;
2001 } else {
2002 die $err;
2003 }
2004 }
2005 });
9622e848
DM
2006}
2007
2008sub mount_all {
7b49dfe0 2009 my ($vmid, $storage_cfg, $conf) = @_;
9622e848
DM
2010
2011 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1adc7e53 2012 File::Path::make_path($rootdir);
9622e848
DM
2013
2014 my $volid_list = get_vm_volumes($conf);
2015 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
2016
2017 eval {
9622e848
DM
2018 foreach_mountpoint($conf, sub {
2019 my ($ms, $mountpoint) = @_;
2020
2021 my $volid = $mountpoint->{volume};
2022 my $mount = $mountpoint->{mp};
2023
2024 return if !$volid || !$mount;
2025
2026 my $image_path = PVE::Storage::path($storage_cfg, $volid);
2027 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2028 PVE::Storage::parse_volname($storage_cfg, $volid);
2029
2030 die "unable to mount base volume - internal error" if $isBase;
2031
da629848 2032 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
9622e848
DM
2033 });
2034 };
2035 if (my $err = $@) {
2036 warn "mounting container failed - $err";
2037 umount_all($vmid, $storage_cfg, $conf, 1);
9622e848
DM
2038 }
2039
da629848 2040 return $rootdir;
9622e848
DM
2041}
2042
2043
b15c75fc 2044sub mountpoint_mount_path {
da629848 2045 my ($mountpoint, $storage_cfg, $snapname) = @_;
b15c75fc 2046
da629848 2047 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
b15c75fc 2048}
cc6b0307 2049
2cfae16e
WB
2050my $check_mount_path = sub {
2051 my ($path) = @_;
2052 $path = File::Spec->canonpath($path);
2053 my $real = Cwd::realpath($path);
2054 if ($real ne $path) {
2055 die "mount path modified by symlink: $path != $real";
2056 }
2057};
2058
b15c75fc 2059# use $rootdir = undef to just return the corresponding mount path
cc6b0307 2060sub mountpoint_mount {
da629848 2061 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
cc6b0307
AD
2062
2063 my $volid = $mountpoint->{volume};
2064 my $mount = $mountpoint->{mp};
b15c75fc 2065
cc6b0307
AD
2066 return if !$volid || !$mount;
2067
b15c75fc
DM
2068 my $mount_path;
2069
2070 if (defined($rootdir)) {
2071 $rootdir =~ s!/+$!!;
2072 $mount_path = "$rootdir/$mount";
f845a93d 2073 $mount_path =~ s!/+!/!g;
2cfae16e 2074 &$check_mount_path($mount_path);
b15c75fc 2075 File::Path::mkpath($mount_path);
116ce06f 2076 }
b15c75fc
DM
2077
2078 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
cc6b0307 2079
b15c75fc 2080 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
cc6b0307 2081
b15c75fc
DM
2082 if ($storage) {
2083
2084 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
2085 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
2086
2087 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2088 PVE::Storage::parse_volname($storage_cfg, $volid);
2089
2090 if ($format eq 'subvol') {
30de33be
DM
2091 if ($mount_path) {
2092 if ($snapname) {
2093 if ($scfg->{type} eq 'zfspool') {
2094 my $path_arg = $path;
2095 $path_arg =~ s!^/+!!;
9d7d4d30 2096 PVE::Tools::run_command(['mount', '-o', 'ro,noload', '-t', 'zfs', $path_arg, $mount_path]);
30de33be
DM
2097 } else {
2098 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2099 }
b15c75fc 2100 } else {
30de33be
DM
2101 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
2102 }
b15c75fc 2103 }
30de33be 2104 return wantarray ? ($path, 0) : $path;
b15c75fc 2105 } elsif ($format eq 'raw') {
30de33be 2106 my $use_loopdev = 0;
da629848 2107 my @extra_opts;
b15c75fc 2108 if ($scfg->{path}) {
da629848 2109 push @extra_opts, '-o', 'loop';
30de33be 2110 $use_loopdev = 1;
23e3abef 2111 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
b15c75fc
DM
2112 # do nothing
2113 } else {
2114 die "unsupported storage type '$scfg->{type}'\n";
2115 }
30de33be
DM
2116 if ($mount_path) {
2117 if ($isBase || defined($snapname)) {
9d7d4d30 2118 PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
30de33be
DM
2119 } else {
2120 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2121 }
b15c75fc 2122 }
30de33be 2123 return wantarray ? ($path, $use_loopdev) : $path;
b15c75fc
DM
2124 } else {
2125 die "unsupported image format '$format'\n";
2126 }
2127 } elsif ($volid =~ m|^/dev/.+|) {
2128 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
30de33be 2129 return wantarray ? ($volid, 0) : $volid;
b15c75fc 2130 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2cfae16e 2131 &$check_mount_path($volid);
b15c75fc 2132 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
30de33be 2133 return wantarray ? ($volid, 0) : $volid;
b15c75fc
DM
2134 }
2135
2136 die "unsupported storage";
cc6b0307
AD
2137}
2138
9205e9d0
AD
2139sub get_vm_volumes {
2140 my ($conf, $excludes) = @_;
2141
2142 my $vollist = [];
2143
706c9791 2144 foreach_mountpoint($conf, sub {
9205e9d0
AD
2145 my ($ms, $mountpoint) = @_;
2146
2147 return if $excludes && $ms eq $excludes;
2148
2149 my $volid = $mountpoint->{volume};
2150
2151 return if !$volid || $volid =~ m|^/|;
2152
2153 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2154 return if !$sid;
2155
2156 push @$vollist, $volid;
2157 });
2158
2159 return $vollist;
2160}
2161
6c871c36
DM
2162sub mkfs {
2163 my ($dev) = @_;
2164
2165 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp', $dev]);
2166}
2167
2168sub format_disk {
2169 my ($storage_cfg, $volid) = @_;
2170
2171 if ($volid =~ m!^/dev/.+!) {
2172 mkfs($volid);
2173 return;
2174 }
2175
2176 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2177
2178 die "cannot format volume '$volid' with no storage\n" if !$storage;
2179
08ca136d
DM
2180 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2181
6c871c36
DM
2182 my $path = PVE::Storage::path($storage_cfg, $volid);
2183
2184 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2185 PVE::Storage::parse_volname($storage_cfg, $volid);
2186
2187 die "cannot format volume '$volid' (format == $format)\n"
2188 if $format ne 'raw';
2189
2190 mkfs($path);
2191}
2192
2193sub destroy_disks {
2194 my ($storecfg, $vollist) = @_;
2195
2196 foreach my $volid (@$vollist) {
2197 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2198 warn $@ if $@;
2199 }
2200}
2201
2202sub create_disks {
2203 my ($storecfg, $vmid, $settings, $conf) = @_;
2204
2205 my $vollist = [];
2206
2207 eval {
2208 foreach_mountpoint($settings, sub {
2209 my ($ms, $mountpoint) = @_;
2210
2211 my $volid = $mountpoint->{volume};
2212 my $mp = $mountpoint->{mp};
2213
2214 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2215
2216 return if !$storage;
2217
2218 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
8ed5ff9d 2219 my ($storeid, $size_gb) = ($1, $2);
6c871c36 2220
8ed5ff9d 2221 my $size_kb = int(${size_gb}*1024) * 1024;
6c871c36
DM
2222
2223 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2224 # fixme: use better naming ct-$vmid-disk-X.raw?
2225
2226 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
8ed5ff9d 2227 if ($size_kb > 0) {
6c871c36 2228 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
8ed5ff9d 2229 undef, $size_kb);
6c871c36
DM
2230 format_disk($storecfg, $volid);
2231 } else {
2232 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2233 undef, 0);
2234 }
2235 } elsif ($scfg->{type} eq 'zfspool') {
2236
2237 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
8ed5ff9d 2238 undef, $size_kb);
23e3abef 2239 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
6c871c36 2240
8ed5ff9d 2241 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
6c871c36
DM
2242 format_disk($storecfg, $volid);
2243
2244 } elsif ($scfg->{type} eq 'rbd') {
2245
2246 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
8ed5ff9d 2247 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
6c871c36
DM
2248 format_disk($storecfg, $volid);
2249 } else {
2250 die "unable to create containers on storage type '$scfg->{type}'\n";
2251 }
2252 push @$vollist, $volid;
8ed5ff9d 2253 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
ab4232be 2254 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
6c871c36
DM
2255 } else {
2256 # use specified/existing volid
2257 }
2258 });
2259 };
2260 # free allocated images on error
2261 if (my $err = $@) {
2262 destroy_disks($storecfg, $vollist);
2263 die $err;
2264 }
2265 return $vollist;
2266}
2267
68e8f3c5
DM
2268# bash completion helper
2269
2270sub complete_os_templates {
2271 my ($cmdname, $pname, $cvalue) = @_;
2272
2273 my $cfg = PVE::Storage::config();
2274
9e9bc3a6 2275 my $storeid;
68e8f3c5
DM
2276
2277 if ($cvalue =~ m/^([^:]+):/) {
2278 $storeid = $1;
2279 }
2280
2281 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2282 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2283
2284 my $res = [];
2285 foreach my $id (keys %$data) {
2286 foreach my $item (@{$data->{$id}}) {
2287 push @$res, $item->{volid} if defined($item->{volid});
2288 }
2289 }
2290
2291 return $res;
2292}
2293
68e8f3c5
DM
2294my $complete_ctid_full = sub {
2295 my ($running) = @_;
2296
2297 my $idlist = vmstatus();
2298
2299 my $active_hash = list_active_containers();
2300
2301 my $res = [];
2302
2303 foreach my $id (keys %$idlist) {
2304 my $d = $idlist->{$id};
2305 if (defined($running)) {
2306 next if $d->{template};
2307 next if $running && !$active_hash->{$id};
2308 next if !$running && $active_hash->{$id};
2309 }
2310 push @$res, $id;
2311
2312 }
2313 return $res;
2314};
2315
2316sub complete_ctid {
2317 return &$complete_ctid_full();
2318}
2319
2320sub complete_ctid_stopped {
2321 return &$complete_ctid_full(0);
2322}
2323
2324sub complete_ctid_running {
2325 return &$complete_ctid_full(1);
2326}
2327
f76a2828 23281;