]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
VZDump lock update, drop lock_aquire/lock_release
[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;
1798
a92f66c9
WL
1799 eval {
1800 if ($running) {
4db769cf 1801 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
2477a7f1 1802 $unfreeze = 1;
4db769cf 1803 PVE::Tools::run_command(['/bin/sync']);
a92f66c9
WL
1804 };
1805
1806 my $storecfg = PVE::Storage::config();
44a9face 1807 my $rootinfo = parse_ct_rootfs($conf->{rootfs});
09d3ec42 1808 my $volid = $rootinfo->{volume};
a92f66c9 1809
a92f66c9
WL
1810 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1811 &$snapshot_commit($vmid, $snapname);
1812 };
2477a7f1
DM
1813 my $err = $@;
1814
1815 if ($unfreeze) {
1816 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
1817 warn $@ if $@;
1818 }
1819
1820 if ($err) {
31429832 1821 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1822 die "$err\n";
1823 }
68a05bb3
AD
1824}
1825
57ccb3f8
WL
1826sub snapshot_delete {
1827 my ($vmid, $snapname, $force) = @_;
1828
31429832
WL
1829 my $snap;
1830
1831 my $conf;
1832
1833 my $updatefn = sub {
1834
1835 $conf = load_config($vmid);
1836
bb1ac2de
DM
1837 die "you can't delete a snapshot if vm is a template\n"
1838 if is_template($conf);
1839
31429832
WL
1840 $snap = $conf->{snapshots}->{$snapname};
1841
1842 check_lock($conf);
1843
1844 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1845
09d3ec42 1846 $snap->{snapstate} = 'delete';
31429832 1847
706c9791 1848 write_config($vmid, $conf);
31429832
WL
1849 };
1850
1851 lock_container($vmid, 10, $updatefn);
1852
1853 my $storecfg = PVE::Storage::config();
1854
7b2eb379 1855 my $unlink_parent = sub {
31429832 1856
7b2eb379 1857 my ($confref, $new_parent) = @_;
31429832 1858
7b2eb379
FG
1859 if ($confref->{parent} && $confref->{parent} eq $snapname) {
1860 if ($new_parent) {
1861 $confref->{parent} = $new_parent;
31429832 1862 } else {
7b2eb379 1863 delete $confref->{parent};
31429832
WL
1864 }
1865 }
7b2eb379
FG
1866 };
1867
1868 my $del_snap = sub {
1869
1870 check_lock($conf);
1871
1872 my $parent = $conf->{snapshots}->{$snapname}->{parent};
1873 foreach my $snapkey (keys %{$conf->{snapshots}}) {
1874 &$unlink_parent($conf->{snapshots}->{$snapkey}, $parent);
1875 }
1876
1877 &$unlink_parent($conf, $parent);
31429832
WL
1878
1879 delete $conf->{snapshots}->{$snapname};
1880
706c9791 1881 write_config($vmid, $conf);
31429832
WL
1882 };
1883
09d3ec42 1884 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
44a9face 1885 my $rootinfo = parse_ct_rootfs($rootfs);
09d3ec42 1886 my $volid = $rootinfo->{volume};
31429832
WL
1887
1888 eval {
1889 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1890 };
1891 my $err = $@;
1892
1893 if(!$err || ($err && $force)) {
1894 lock_container($vmid, 10, $del_snap);
1895 if ($err) {
1896 die "Can't delete snapshot: $vmid $snapname $err\n";
1897 }
1898 }
57ccb3f8
WL
1899}
1900
723157f6
WL
1901sub snapshot_rollback {
1902 my ($vmid, $snapname) = @_;
1903
6860ba0c
WL
1904 my $storecfg = PVE::Storage::config();
1905
1906 my $conf = load_config($vmid);
1907
bb1ac2de
DM
1908 die "you can't rollback if vm is a template\n" if is_template($conf);
1909
6860ba0c
WL
1910 my $snap = $conf->{snapshots}->{$snapname};
1911
1912 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1913
09d3ec42 1914 my $rootfs = $snap->{rootfs};
44a9face 1915 my $rootinfo = parse_ct_rootfs($rootfs);
09d3ec42
DM
1916 my $volid = $rootinfo->{volume};
1917
1918 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1919
1920 my $updatefn = sub {
1921
09d3ec42
DM
1922 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1923 if $snap->{snapstate};
6860ba0c
WL
1924
1925 check_lock($conf);
6860ba0c 1926
b935932a 1927 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1928
1929 die "unable to rollback vm $vmid: vm is running\n"
1930 if check_running($vmid);
1931
09d3ec42 1932 $conf->{lock} = 'rollback';
6860ba0c
WL
1933
1934 my $forcemachine;
1935
1936 # copy snapshot config to current config
1937
1938 my $tmp_conf = $conf;
1939 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1940 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1941 delete $conf->{snaptime};
1942 delete $conf->{snapname};
1943 $conf->{parent} = $snapname;
6860ba0c 1944
706c9791 1945 write_config($vmid, $conf);
6860ba0c
WL
1946 };
1947
1948 my $unlockfn = sub {
09d3ec42 1949 delete $conf->{lock};
706c9791 1950 write_config($vmid, $conf);
6860ba0c
WL
1951 };
1952
1953 lock_container($vmid, 10, $updatefn);
1954
09d3ec42 1955 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1956
1957 lock_container($vmid, 5, $unlockfn);
723157f6 1958}
b935932a 1959
bb1ac2de
DM
1960sub template_create {
1961 my ($vmid, $conf) = @_;
1962
1963 my $storecfg = PVE::Storage::config();
1964
44a9face 1965 my $rootinfo = parse_ct_rootfs($conf->{rootfs});
bb1ac2de
DM
1966 my $volid = $rootinfo->{volume};
1967
1968 die "Template feature is not available for '$volid'\n"
1969 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1970
1971 PVE::Storage::activate_volumes($storecfg, [$volid]);
1972
1973 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1974 $rootinfo->{volume} = $template_volid;
4fee75fd 1975 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
bb1ac2de
DM
1976
1977 write_config($vmid, $conf);
1978}
1979
1980sub is_template {
1981 my ($conf) = @_;
1982
1983 return 1 if defined $conf->{template} && $conf->{template} == 1;
1984}
1985
9622e848
DM
1986sub mountpoint_names {
1987 my ($reverse) = @_;
ced7fddb 1988
9622e848 1989 my @names = ('rootfs');
eaebef36
DM
1990
1991 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
9622e848
DM
1992 push @names, "mp$i";
1993 }
1994
1995 return $reverse ? reverse @names : @names;
1996}
1997
3c9dbfa9 1998
9622e848
DM
1999sub foreach_mountpoint_full {
2000 my ($conf, $reverse, $func) = @_;
2001
2002 foreach my $key (mountpoint_names($reverse)) {
2003 my $value = $conf->{$key};
2004 next if !defined($value);
44a9face 2005 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs($value, 1) : parse_ct_mountpoint($value, 1);
ca7feb1a 2006 next if !defined($mountpoint);
3c9dbfa9 2007
eaebef36 2008 &$func($key, $mountpoint);
ced7fddb
AD
2009 }
2010}
2011
9622e848
DM
2012sub foreach_mountpoint {
2013 my ($conf, $func) = @_;
2014
2015 foreach_mountpoint_full($conf, 0, $func);
2016}
2017
2018sub foreach_mountpoint_reverse {
2019 my ($conf, $func) = @_;
2020
2021 foreach_mountpoint_full($conf, 1, $func);
2022}
2023
52389a07
DM
2024sub check_ct_modify_config_perm {
2025 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2026
2027 return 1 if $authuser ne 'root@pam';
2028
2029 foreach my $opt (@$key_list) {
2030
2031 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2032 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
e59a61ed 2033 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
52389a07
DM
2034 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2035 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2036 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2037 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2038 $opt eq 'searchdomain' || $opt eq 'hostname') {
2039 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2040 } else {
2041 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2042 }
2043 }
2044
2045 return 1;
2046}
2047
9622e848 2048sub umount_all {
da629848 2049 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
9622e848
DM
2050
2051 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2052 my $volid_list = get_vm_volumes($conf);
2053
2054 foreach_mountpoint_reverse($conf, sub {
2055 my ($ms, $mountpoint) = @_;
2056
2057 my $volid = $mountpoint->{volume};
2058 my $mount = $mountpoint->{mp};
2059
2060 return if !$volid || !$mount;
2061
d18f96b4 2062 my $mount_path = "$rootdir/$mount";
f845a93d 2063 $mount_path =~ s!/+!/!g;
9622e848 2064
228a5a1d
WL
2065 return if !PVE::ProcFSTools::is_mounted($mount_path);
2066
9622e848 2067 eval {
d18f96b4 2068 PVE::Tools::run_command(['umount', '-d', $mount_path]);
9622e848
DM
2069 };
2070 if (my $err = $@) {
2071 if ($noerr) {
2072 warn $err;
2073 } else {
2074 die $err;
2075 }
2076 }
2077 });
9622e848
DM
2078}
2079
2080sub mount_all {
7b49dfe0 2081 my ($vmid, $storage_cfg, $conf) = @_;
9622e848
DM
2082
2083 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1adc7e53 2084 File::Path::make_path($rootdir);
9622e848
DM
2085
2086 my $volid_list = get_vm_volumes($conf);
2087 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
2088
2089 eval {
9622e848
DM
2090 foreach_mountpoint($conf, sub {
2091 my ($ms, $mountpoint) = @_;
2092
da629848 2093 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
9622e848
DM
2094 });
2095 };
2096 if (my $err = $@) {
e2007ac2 2097 warn "mounting container failed\n";
9622e848 2098 umount_all($vmid, $storage_cfg, $conf, 1);
e2007ac2 2099 die $err;
9622e848
DM
2100 }
2101
da629848 2102 return $rootdir;
9622e848
DM
2103}
2104
2105
b15c75fc 2106sub mountpoint_mount_path {
da629848 2107 my ($mountpoint, $storage_cfg, $snapname) = @_;
b15c75fc 2108
da629848 2109 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
b15c75fc 2110}
cc6b0307 2111
2cfae16e
WB
2112my $check_mount_path = sub {
2113 my ($path) = @_;
2114 $path = File::Spec->canonpath($path);
2115 my $real = Cwd::realpath($path);
2116 if ($real ne $path) {
2117 die "mount path modified by symlink: $path != $real";
2118 }
2119};
2120
21f292ff
WB
2121sub query_loopdev {
2122 my ($path) = @_;
2123 my $found;
2124 my $parser = sub {
2125 my $line = shift;
2126 if ($line =~ m@^(/dev/loop\d+):@) {
2127 $found = $1;
2128 }
2129 };
2130 my $cmd = ['losetup', '--associated', $path];
2131 PVE::Tools::run_command($cmd, outfunc => $parser);
2132 return $found;
2133}
2134
b15c75fc 2135# use $rootdir = undef to just return the corresponding mount path
cc6b0307 2136sub mountpoint_mount {
da629848 2137 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
cc6b0307
AD
2138
2139 my $volid = $mountpoint->{volume};
2140 my $mount = $mountpoint->{mp};
7c921c80 2141 my $type = $mountpoint->{type};
b15c75fc 2142
cc6b0307
AD
2143 return if !$volid || !$mount;
2144
b15c75fc
DM
2145 my $mount_path;
2146
2147 if (defined($rootdir)) {
2148 $rootdir =~ s!/+$!!;
2149 $mount_path = "$rootdir/$mount";
f845a93d 2150 $mount_path =~ s!/+!/!g;
2cfae16e 2151 &$check_mount_path($mount_path);
b15c75fc 2152 File::Path::mkpath($mount_path);
116ce06f 2153 }
b15c75fc
DM
2154
2155 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
cc6b0307 2156
b15c75fc 2157 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
cc6b0307 2158
471dd315
WB
2159 my $optstring = '';
2160 if (defined($mountpoint->{acl})) {
2161 $optstring .= ($mountpoint->{acl} ? 'acl' : 'noacl');
2162 }
2163 if ($mountpoint->{ro}) {
2164 $optstring .= ',' if $optstring;
2165 $optstring .= 'ro';
2166 }
2167
2168 my @extra_opts = ('-o', $optstring);
2169
b15c75fc
DM
2170 if ($storage) {
2171
2172 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
2173 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
2174
2175 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2176 PVE::Storage::parse_volname($storage_cfg, $volid);
2177
c87b9dd8
DM
2178 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2179
b15c75fc 2180 if ($format eq 'subvol') {
30de33be
DM
2181 if ($mount_path) {
2182 if ($snapname) {
e84f7f5d
DM
2183 if ($scfg->{type} eq 'zfspool') {
2184 my $path_arg = $path;
2185 $path_arg =~ s!^/+!!;
471dd315 2186 PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
e84f7f5d 2187 } else {
30de33be
DM
2188 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2189 }
e84f7f5d 2190 } else {
471dd315
WB
2191 if ($mountpoint->{ro}) {
2192 die "read-only bind mounts not supported\n";
2193 }
2194 PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $path, $mount_path]);
30de33be 2195 }
b15c75fc 2196 }
30de33be 2197 return wantarray ? ($path, 0) : $path;
c87b9dd8 2198 } elsif ($format eq 'raw' || $format eq 'iso') {
30de33be 2199 my $use_loopdev = 0;
b15c75fc 2200 if ($scfg->{path}) {
da629848 2201 push @extra_opts, '-o', 'loop';
30de33be 2202 $use_loopdev = 1;
2e879877
DM
2203 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' ||
2204 $scfg->{type} eq 'rbd' || $scfg->{type} eq 'lvmthin') {
b15c75fc
DM
2205 # do nothing
2206 } else {
2207 die "unsupported storage type '$scfg->{type}'\n";
2208 }
30de33be 2209 if ($mount_path) {
c87b9dd8
DM
2210 if ($format eq 'iso') {
2211 PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2212 } elsif ($isBase || defined($snapname)) {
9d7d4d30 2213 PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
30de33be
DM
2214 } else {
2215 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2216 }
b15c75fc 2217 }
30de33be 2218 return wantarray ? ($path, $use_loopdev) : $path;
b15c75fc
DM
2219 } else {
2220 die "unsupported image format '$format'\n";
2221 }
7c921c80 2222 } elsif ($type eq 'device') {
471dd315 2223 PVE::Tools::run_command(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
30de33be 2224 return wantarray ? ($volid, 0) : $volid;
e2007ac2 2225 } elsif ($type eq 'bind') {
471dd315
WB
2226 if ($mountpoint->{ro}) {
2227 die "read-only bind mounts not supported\n";
2228 # Theoretically we'd have to execute both:
2229 # mount -o bind $a $b
2230 # mount -o bind,remount,ro $a $b
2231 }
e2007ac2 2232 die "directory '$volid' does not exist\n" if ! -d $volid;
2cfae16e 2233 &$check_mount_path($volid);
471dd315 2234 PVE::Tools::run_command(['mount', '-o', 'bind', @extra_opts, $volid, $mount_path]) if $mount_path;
30de33be 2235 return wantarray ? ($volid, 0) : $volid;
b15c75fc
DM
2236 }
2237
2238 die "unsupported storage";
cc6b0307
AD
2239}
2240
9205e9d0
AD
2241sub get_vm_volumes {
2242 my ($conf, $excludes) = @_;
2243
2244 my $vollist = [];
2245
706c9791 2246 foreach_mountpoint($conf, sub {
9205e9d0
AD
2247 my ($ms, $mountpoint) = @_;
2248
2249 return if $excludes && $ms eq $excludes;
2250
2251 my $volid = $mountpoint->{volume};
2252
7c921c80 2253 return if !$volid || $mountpoint->{type} ne 'volume';
9205e9d0
AD
2254
2255 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2256 return if !$sid;
2257
2258 push @$vollist, $volid;
2259 });
2260
2261 return $vollist;
2262}
2263
6c871c36 2264sub mkfs {
d216e891 2265 my ($dev, $rootuid, $rootgid) = @_;
6c871c36 2266
d216e891
WB
2267 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp',
2268 '-E', "root_owner=$rootuid:$rootgid",
2269 $dev]);
6c871c36
DM
2270}
2271
2272sub format_disk {
d216e891 2273 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
6c871c36
DM
2274
2275 if ($volid =~ m!^/dev/.+!) {
2276 mkfs($volid);
2277 return;
2278 }
2279
2280 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2281
2282 die "cannot format volume '$volid' with no storage\n" if !$storage;
2283
08ca136d
DM
2284 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2285
6c871c36
DM
2286 my $path = PVE::Storage::path($storage_cfg, $volid);
2287
2288 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2289 PVE::Storage::parse_volname($storage_cfg, $volid);
2290
2291 die "cannot format volume '$volid' (format == $format)\n"
2292 if $format ne 'raw';
2293
d216e891 2294 mkfs($path, $rootuid, $rootgid);
6c871c36
DM
2295}
2296
2297sub destroy_disks {
2298 my ($storecfg, $vollist) = @_;
2299
2300 foreach my $volid (@$vollist) {
2301 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2302 warn $@ if $@;
2303 }
2304}
2305
2306sub create_disks {
2307 my ($storecfg, $vmid, $settings, $conf) = @_;
2308
2309 my $vollist = [];
2310
2311 eval {
d216e891
WB
2312 my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
2313 my $chown_vollist = [];
2314
6c871c36
DM
2315 foreach_mountpoint($settings, sub {
2316 my ($ms, $mountpoint) = @_;
2317
2318 my $volid = $mountpoint->{volume};
2319 my $mp = $mountpoint->{mp};
2320
2321 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2322
e2007ac2 2323 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
8ed5ff9d 2324 my ($storeid, $size_gb) = ($1, $2);
6c871c36 2325
8ed5ff9d 2326 my $size_kb = int(${size_gb}*1024) * 1024;
6c871c36
DM
2327
2328 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2329 # fixme: use better naming ct-$vmid-disk-X.raw?
2330
2331 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
8ed5ff9d 2332 if ($size_kb > 0) {
6c871c36 2333 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
8ed5ff9d 2334 undef, $size_kb);
d216e891 2335 format_disk($storecfg, $volid, $rootuid, $rootgid);
6c871c36
DM
2336 } else {
2337 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2338 undef, 0);
d216e891 2339 push @$chown_vollist, $volid;
6c871c36
DM
2340 }
2341 } elsif ($scfg->{type} eq 'zfspool') {
2342
2343 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
8ed5ff9d 2344 undef, $size_kb);
d216e891 2345 push @$chown_vollist, $volid;
2e879877 2346 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'lvmthin') {
6c871c36 2347
8ed5ff9d 2348 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
d216e891 2349 format_disk($storecfg, $volid, $rootuid, $rootgid);
6c871c36
DM
2350
2351 } elsif ($scfg->{type} eq 'rbd') {
2352
2353 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
8ed5ff9d 2354 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
d216e891 2355 format_disk($storecfg, $volid, $rootuid, $rootgid);
6c871c36
DM
2356 } else {
2357 die "unable to create containers on storage type '$scfg->{type}'\n";
2358 }
2359 push @$vollist, $volid;
71c780b9
WB
2360 $mountpoint->{volume} = $volid;
2361 $mountpoint->{size} = $size_kb * 1024;
2362 $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
6c871c36 2363 } else {
e2007ac2
DM
2364 # use specified/existing volid/dir/device
2365 $conf->{$ms} = print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
6c871c36
DM
2366 }
2367 });
d216e891
WB
2368
2369 PVE::Storage::activate_volumes($storecfg, $chown_vollist, undef);
2370 foreach my $volid (@$chown_vollist) {
2371 my $path = PVE::Storage::path($storecfg, $volid, undef);
2372 chown($rootuid, $rootgid, $path);
2373 }
2374 PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef);
6c871c36
DM
2375 };
2376 # free allocated images on error
2377 if (my $err = $@) {
2378 destroy_disks($storecfg, $vollist);
2379 die $err;
2380 }
2381 return $vollist;
2382}
2383
68e8f3c5
DM
2384# bash completion helper
2385
2386sub complete_os_templates {
2387 my ($cmdname, $pname, $cvalue) = @_;
2388
2389 my $cfg = PVE::Storage::config();
2390
9e9bc3a6 2391 my $storeid;
68e8f3c5
DM
2392
2393 if ($cvalue =~ m/^([^:]+):/) {
2394 $storeid = $1;
2395 }
2396
2397 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2398 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2399
2400 my $res = [];
2401 foreach my $id (keys %$data) {
2402 foreach my $item (@{$data->{$id}}) {
2403 push @$res, $item->{volid} if defined($item->{volid});
2404 }
2405 }
2406
2407 return $res;
2408}
2409
68e8f3c5
DM
2410my $complete_ctid_full = sub {
2411 my ($running) = @_;
2412
2413 my $idlist = vmstatus();
2414
2415 my $active_hash = list_active_containers();
2416
2417 my $res = [];
2418
2419 foreach my $id (keys %$idlist) {
2420 my $d = $idlist->{$id};
2421 if (defined($running)) {
2422 next if $d->{template};
2423 next if $running && !$active_hash->{$id};
2424 next if !$running && $active_hash->{$id};
2425 }
2426 push @$res, $id;
2427
2428 }
2429 return $res;
2430};
2431
2432sub complete_ctid {
2433 return &$complete_ctid_full();
2434}
2435
2436sub complete_ctid_stopped {
2437 return &$complete_ctid_full(0);
2438}
2439
2440sub complete_ctid_running {
2441 return &$complete_ctid_full(1);
2442}
2443
c6a605f9
WB
2444sub parse_id_maps {
2445 my ($conf) = @_;
2446
2447 my $id_map = [];
2448 my $rootuid = 0;
2449 my $rootgid = 0;
2450
2451 my $lxc = $conf->{lxc};
2452 foreach my $entry (@$lxc) {
2453 my ($key, $value) = @$entry;
2454 next if $key ne 'lxc.id_map';
2455 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2456 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2457 push @$id_map, [$type, $ct, $host, $length];
2458 if ($ct == 0) {
2459 $rootuid = $host if $type eq 'u';
2460 $rootgid = $host if $type eq 'g';
2461 }
2462 } else {
2463 die "failed to parse id_map: $value\n";
2464 }
2465 }
2466
2467 if (!@$id_map && $conf->{unprivileged}) {
2468 # Should we read them from /etc/subuid?
2469 $id_map = [ ['u', '0', '100000', '65536'],
2470 ['g', '0', '100000', '65536'] ];
2471 $rootuid = $rootgid = 100000;
2472 }
2473
2474 return ($id_map, $rootuid, $rootgid);
2475}
2476
01dce99b
WB
2477sub userns_command {
2478 my ($id_map) = @_;
2479 if (@$id_map) {
2480 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];
2481 }
2482 return [];
2483}
2484
f76a2828 24851;