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