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