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