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