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