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