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