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