]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC.pm
factor query_loopdev into PVE::LXC
[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',
c0821a36 101 enum => ['debian', 'ubuntu', 'centos', '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
27916659 905sub parse_ct_mountpoint {
ca7feb1a 906 my ($data, $noerr) = @_;
27916659
DM
907
908 $data //= '';
909
1b2c1e8c
WB
910 my $res;
911 eval { $res = PVE::JSONSchema::parse_property_string($mp_desc, $data) };
912 if ($@) {
ca7feb1a
WB
913 return undef if $noerr;
914 die $@;
27916659
DM
915 }
916
bf4a209a 917 if (defined(my $size = $res->{size})) {
ca7feb1a
WB
918 $size = PVE::JSONSchema::parse_size($size);
919 if (!defined($size)) {
920 return undef if $noerr;
921 die "invalid size: $size\n";
922 }
923 $res->{size} = $size;
27916659
DM
924 }
925
926 return $res;
927}
7dfc49cc 928
dde7b02b 929sub print_ct_mountpoint {
4fee75fd 930 my ($info, $nomp) = @_;
6708ba93
WB
931 my $skip = $nomp ? ['mp'] : [];
932 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
bb1ac2de
DM
933}
934
7dfc49cc 935sub print_lxc_network {
f76a2828 936 my $net = shift;
6708ba93 937 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
f76a2828
DM
938}
939
7dfc49cc
DM
940sub parse_lxc_network {
941 my ($data) = @_;
942
943 my $res = {};
944
945 return $res if !$data;
946
ca7feb1a 947 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
7dfc49cc
DM
948
949 $res->{type} = 'veth';
93cdbbfb 950 $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
cbb03fea 951
7dfc49cc
DM
952 return $res;
953}
f76a2828 954
238a56cb
DM
955sub read_cgroup_value {
956 my ($group, $vmid, $name, $full) = @_;
957
958 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
959
960 return PVE::Tools::file_get_contents($path) if $full;
961
962 return PVE::Tools::file_read_firstline($path);
963}
964
bf0b8c43
AD
965sub write_cgroup_value {
966 my ($group, $vmid, $name, $value) = @_;
967
968 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
969 PVE::ProcFSTools::write_proc_entry($path, $value) if -e $path;
970
971}
972
52f1d76b
DM
973sub find_lxc_console_pids {
974
975 my $res = {};
976
977 PVE::Tools::dir_glob_foreach('/proc', '\d+', sub {
978 my ($pid) = @_;
979
980 my $cmdline = PVE::Tools::file_read_firstline("/proc/$pid/cmdline");
981 return if !$cmdline;
982
983 my @args = split(/\0/, $cmdline);
984
985 # serach for lxc-console -n <vmid>
cbb03fea 986 return if scalar(@args) != 3;
52f1d76b
DM
987 return if $args[1] ne '-n';
988 return if $args[2] !~ m/^\d+$/;
989 return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
cbb03fea 990
52f1d76b 991 my $vmid = $args[2];
cbb03fea 992
52f1d76b
DM
993 push @{$res->{$vmid}}, $pid;
994 });
995
996 return $res;
997}
998
bedeaaf1
AD
999sub find_lxc_pid {
1000 my ($vmid) = @_;
1001
1002 my $pid = undef;
1003 my $parser = sub {
1004 my $line = shift;
8b25977f 1005 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
bedeaaf1 1006 };
c39aa40a 1007 PVE::Tools::run_command(['lxc-info', '-n', $vmid, '-p'], outfunc => $parser);
bedeaaf1 1008
8b25977f 1009 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
cbb03fea 1010
8b25977f 1011 return $pid;
bedeaaf1
AD
1012}
1013
cbb03fea 1014# Note: we cannot use Net:IP, because that only allows strict
55fa4e09
DM
1015# CIDR networks
1016sub parse_ipv4_cidr {
1017 my ($cidr, $noerr) = @_;
1018
f7a7b413
WB
1019 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1020 return { address => $1, netmask => $PVE::Network::ipv4_reverse_mask->[$2] };
55fa4e09 1021 }
cbb03fea 1022
55fa4e09 1023 return undef if $noerr;
cbb03fea 1024
55fa4e09
DM
1025 die "unable to parse ipv4 address/mask\n";
1026}
93285df8 1027
a12a36e0
WL
1028sub check_lock {
1029 my ($conf) = @_;
1030
27916659 1031 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
a12a36e0
WL
1032}
1033
e22af68f
AG
1034sub check_protection {
1035 my ($vm_conf, $err_msg) = @_;
1036
1037 if ($vm_conf->{protection}) {
1038 die "$err_msg - protection mode enabled\n";
1039 }
1040}
1041
27916659 1042sub update_lxc_config {
c628ffa1 1043 my ($storage_cfg, $vmid, $conf) = @_;
b80dd50a 1044
bb1ac2de
DM
1045 my $dir = "/var/lib/lxc/$vmid";
1046
1047 if ($conf->{template}) {
1048
1049 unlink "$dir/config";
1050
1051 return;
1052 }
1053
27916659 1054 my $raw = '';
b80dd50a 1055
27916659
DM
1056 die "missing 'arch' - internal error" if !$conf->{arch};
1057 $raw .= "lxc.arch = $conf->{arch}\n";
b80dd50a 1058
425b62cb
WB
1059 my $unprivileged = $conf->{unprivileged};
1060 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc}};
1061
27916659 1062 my $ostype = $conf->{ostype} || die "missing 'ostype' - internal error";
c1d32b55 1063 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
27916659 1064 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
425b62cb
WB
1065 if ($unprivileged || $custom_idmap) {
1066 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1067 }
27916659
DM
1068 } else {
1069 die "implement me";
1070 }
b80dd50a 1071
58cc92a9
WB
1072 $raw .= "lxc.start.unshare = 1\n";
1073
425b62cb
WB
1074 # Should we read them from /etc/subuid?
1075 if ($unprivileged && !$custom_idmap) {
1076 $raw .= "lxc.id_map = u 0 100000 65536\n";
1077 $raw .= "lxc.id_map = g 0 100000 65536\n";
1078 }
1079
6f035afe 1080 if (!has_dev_console($conf)) {
eeaea429
DM
1081 $raw .= "lxc.console = none\n";
1082 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1083 }
4f958489 1084
0d0ca400 1085 my $ttycount = get_tty_count($conf);
27916659 1086 $raw .= "lxc.tty = $ttycount\n";
cbb03fea 1087
a691a5a3
DM
1088 # some init scripts expects a linux terminal (turnkey).
1089 $raw .= "lxc.environment = TERM=linux\n";
1090
27916659
DM
1091 my $utsname = $conf->{hostname} || "CT$vmid";
1092 $raw .= "lxc.utsname = $utsname\n";
cbb03fea 1093
27916659
DM
1094 my $memory = $conf->{memory} || 512;
1095 my $swap = $conf->{swap} // 0;
1096
1097 my $lxcmem = int($memory*1024*1024);
1098 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
d4eeba77 1099 $raw .= "lxc.cgroup.memory.kmem.limit_in_bytes = $lxcmem\n";
a12a36e0 1100
27916659
DM
1101 my $lxcswap = int(($memory + $swap)*1024*1024);
1102 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1103
1104 if (my $cpulimit = $conf->{cpulimit}) {
1105 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1106 my $value = int(100000*$cpulimit);
1107 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
a12a36e0
WL
1108 }
1109
27916659
DM
1110 my $shares = $conf->{cpuunits} || 1024;
1111 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1112
21be9680 1113 my $mountpoint = parse_ct_mountpoint($conf->{rootfs});
b15c75fc 1114 $mountpoint->{mp} = '/';
a3076d81 1115
c9a5774b 1116 $raw .= "lxc.rootfs = $dir/rootfs\n";
c0681d66 1117 $raw .= "lxc.hook.stop = /usr/lib/x86_64-linux-gnu/lxc/hooks/unmount-namespace\n";
27916659
DM
1118
1119 my $netcount = 0;
1120 foreach my $k (keys %$conf) {
1121 next if $k !~ m/^net(\d+)$/;
1122 my $ind = $1;
a16d94c8 1123 my $d = parse_lxc_network($conf->{$k});
27916659
DM
1124 $netcount++;
1125 $raw .= "lxc.network.type = veth\n";
18862537 1126 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
27916659
DM
1127 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr});
1128 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name});
1129 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu});
a12a36e0
WL
1130 }
1131
e576f689
DM
1132 if (my $lxcconf = $conf->{lxc}) {
1133 foreach my $entry (@$lxcconf) {
1134 my ($k, $v) = @$entry;
1135 $netcount++ if $k eq 'lxc.network.type';
1136 $raw .= "$k = $v\n";
1137 }
1138 }
27916659 1139
e576f689
DM
1140 $raw .= "lxc.network.type = empty\n" if !$netcount;
1141
27916659
DM
1142 File::Path::mkpath("$dir/rootfs");
1143
1144 PVE::Tools::file_set_contents("$dir/config", $raw);
b80dd50a
DM
1145}
1146
117636e5
DM
1147# verify and cleanup nameserver list (replace \0 with ' ')
1148sub verify_nameserver_list {
1149 my ($nameserver_list) = @_;
1150
1151 my @list = ();
1152 foreach my $server (PVE::Tools::split_list($nameserver_list)) {
1153 PVE::JSONSchema::pve_verify_ip($server);
1154 push @list, $server;
1155 }
1156
1157 return join(' ', @list);
1158}
1159
1160sub verify_searchdomain_list {
1161 my ($searchdomain_list) = @_;
1162
1163 my @list = ();
1164 foreach my $server (PVE::Tools::split_list($searchdomain_list)) {
1165 # todo: should we add checks for valid dns domains?
1166 push @list, $server;
1167 }
1168
1169 return join(' ', @list);
1170}
1171
69202f71
WB
1172sub add_unused_volume {
1173 my ($config, $volid) = @_;
1174
b6c01d29
WB
1175 # skip bind mounts and block devices
1176 return if $volid =~ m|^/|;
1177
69202f71
WB
1178 my $key;
1179 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1180 my $test = "unused$ind";
1181 if (my $vid = $config->{$test}) {
1182 return if $vid eq $volid; # do not add duplicates
1183 } else {
1184 $key = $test;
1185 }
1186 }
1187
1188 die "To many unused volume - please delete them first.\n" if !$key;
1189
1190 $config->{$key} = $volid;
1191
1192 return $key;
1193}
1194
27916659 1195sub update_pct_config {
93285df8
DM
1196 my ($vmid, $conf, $running, $param, $delete) = @_;
1197
bf0b8c43
AD
1198 my @nohotplug;
1199
7b49dfe0 1200 my $new_disks = 0;
69202f71 1201 my @deleted_volumes;
4fee75fd 1202
cbb03fea
DM
1203 my $rootdir;
1204 if ($running) {
bedeaaf1 1205 my $pid = find_lxc_pid($vmid);
cbb03fea 1206 $rootdir = "/proc/$pid/root";
bedeaaf1
AD
1207 }
1208
7a168607
DM
1209 my $hotplug_error = sub {
1210 if ($running) {
a6a77cfa
WB
1211 push @nohotplug, @_;
1212 return 1;
7a168607
DM
1213 } else {
1214 return 0;
a6a77cfa 1215 }
7a168607 1216 };
a6a77cfa 1217
93285df8
DM
1218 if (defined($delete)) {
1219 foreach my $opt (@$delete) {
27916659 1220 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
93285df8
DM
1221 die "unable to delete required option '$opt'\n";
1222 } elsif ($opt eq 'swap') {
27916659 1223 delete $conf->{$opt};
bf0b8c43 1224 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
40603eb3 1225 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
27916659 1226 delete $conf->{$opt};
4f958489 1227 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
40603eb3 1228 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
a6a77cfa 1229 next if $hotplug_error->($opt);
27916659 1230 delete $conf->{$opt};
68fba17b 1231 } elsif ($opt =~ m/^net(\d)$/) {
93285df8 1232 delete $conf->{$opt};
68fba17b
AD
1233 next if !$running;
1234 my $netid = $1;
18862537 1235 PVE::Network::veth_delete("veth${vmid}i$netid");
7e806596
AG
1236 } elsif ($opt eq 'protection') {
1237 delete $conf->{$opt};
69202f71 1238 } elsif ($opt =~ m/^unused(\d+)$/) {
a6a77cfa 1239 next if $hotplug_error->($opt);
69202f71
WB
1240 check_protection($conf, "can't remove CT $vmid drive '$opt'");
1241 push @deleted_volumes, $conf->{$opt};
1242 delete $conf->{$opt};
4fee75fd 1243 } elsif ($opt =~ m/^mp(\d+)$/) {
a6a77cfa 1244 next if $hotplug_error->($opt);
e22af68f 1245 check_protection($conf, "can't remove CT $vmid drive '$opt'");
69202f71
WB
1246 my $mountpoint = parse_ct_mountpoint($conf->{$opt});
1247 add_unused_volume($conf, $mountpoint->{volume});
4fee75fd 1248 delete $conf->{$opt};
425b62cb
WB
1249 } elsif ($opt eq 'unprivileged') {
1250 die "unable to delete read-only option: '$opt'\n";
93285df8
DM
1251 } else {
1252 die "implement me"
1253 }
706c9791 1254 write_config($vmid, $conf) if $running;
93285df8
DM
1255 }
1256 }
1257
be6383d7
WB
1258 # There's no separate swap size to configure, there's memory and "total"
1259 # memory (iow. memory+swap). This means we have to change them together.
27916659
DM
1260 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
1261 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
be6383d7 1262 if (defined($wanted_memory) || defined($wanted_swap)) {
27916659
DM
1263
1264 $wanted_memory //= ($conf->{memory} || 512);
1265 $wanted_swap //= ($conf->{swap} || 0);
1266
1267 my $total = $wanted_memory + $wanted_swap;
1268 if ($running) {
1269 write_cgroup_value("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1270 write_cgroup_value("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
be6383d7 1271 }
27916659
DM
1272 $conf->{memory} = $wanted_memory;
1273 $conf->{swap} = $wanted_swap;
1274
706c9791 1275 write_config($vmid, $conf) if $running;
be6383d7
WB
1276 }
1277
93285df8
DM
1278 foreach my $opt (keys %$param) {
1279 my $value = $param->{$opt};
1280 if ($opt eq 'hostname') {
27916659 1281 $conf->{$opt} = $value;
a99b3509 1282 } elsif ($opt eq 'onboot') {
27916659 1283 $conf->{$opt} = $value ? 1 : 0;
a3249355 1284 } elsif ($opt eq 'startup') {
27916659 1285 $conf->{$opt} = $value;
40603eb3 1286 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
a6a77cfa 1287 next if $hotplug_error->($opt);
e576f689 1288 $conf->{$opt} = $value;
ffa1d001 1289 } elsif ($opt eq 'nameserver') {
a6a77cfa 1290 next if $hotplug_error->($opt);
117636e5 1291 my $list = verify_nameserver_list($value);
27916659 1292 $conf->{$opt} = $list;
ffa1d001 1293 } elsif ($opt eq 'searchdomain') {
a6a77cfa 1294 next if $hotplug_error->($opt);
117636e5 1295 my $list = verify_searchdomain_list($value);
27916659 1296 $conf->{$opt} = $list;
45573f7c 1297 } elsif ($opt eq 'cpulimit') {
a6a77cfa 1298 next if $hotplug_error->($opt); # FIXME: hotplug
27916659 1299 $conf->{$opt} = $value;
b80dd50a 1300 } elsif ($opt eq 'cpuunits') {
27916659 1301 $conf->{$opt} = $value;
bf0b8c43 1302 write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
93285df8 1303 } elsif ($opt eq 'description') {
27916659 1304 $conf->{$opt} = PVE::Tools::encode_text($value);
93285df8
DM
1305 } elsif ($opt =~ m/^net(\d+)$/) {
1306 my $netid = $1;
a16d94c8 1307 my $net = parse_lxc_network($value);
27916659
DM
1308 if (!$running) {
1309 $conf->{$opt} = print_lxc_network($net);
cbb03fea 1310 } else {
bedeaaf1
AD
1311 update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
1312 }
7e806596
AG
1313 } elsif ($opt eq 'protection') {
1314 $conf->{$opt} = $value ? 1 : 0;
4fee75fd 1315 } elsif ($opt =~ m/^mp(\d+)$/) {
a6a77cfa 1316 next if $hotplug_error->($opt);
e22af68f 1317 check_protection($conf, "can't update CT $vmid drive '$opt'");
4fee75fd 1318 $conf->{$opt} = $value;
7b49dfe0 1319 $new_disks = 1;
4fee75fd 1320 } elsif ($opt eq 'rootfs') {
e22af68f 1321 check_protection($conf, "can't update CT $vmid drive '$opt'");
b51a98d4 1322 die "implement me: $opt";
425b62cb
WB
1323 } elsif ($opt eq 'unprivileged') {
1324 die "unable to modify read-only option: '$opt'\n";
93285df8 1325 } else {
a92f66c9 1326 die "implement me: $opt";
93285df8 1327 }
706c9791 1328 write_config($vmid, $conf) if $running;
93285df8 1329 }
bf0b8c43 1330
69202f71
WB
1331 if (@deleted_volumes) {
1332 my $storage_cfg = PVE::Storage::config();
1333 foreach my $volume (@deleted_volumes) {
1334 delete_mountpoint_volume($storage_cfg, $vmid, $volume);
1335 }
1336 }
1337
7b49dfe0 1338 if ($new_disks) {
4fee75fd 1339 my $storage_cfg = PVE::Storage::config();
6c871c36 1340 create_disks($storage_cfg, $vmid, $conf, $conf);
4fee75fd 1341 }
694c25df
WB
1342
1343 # This should be the last thing we do here
1344 if ($running && scalar(@nohotplug)) {
1345 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1346 }
93285df8 1347}
c325b32f 1348
6f035afe
DM
1349sub has_dev_console {
1350 my ($conf) = @_;
1351
1352 return !(defined($conf->{console}) && !$conf->{console});
1353}
1354
0d0ca400
DM
1355sub get_tty_count {
1356 my ($conf) = @_;
1357
1358 return $conf->{tty} // $confdesc->{tty}->{default};
1359}
1360
aca816ad
DM
1361sub get_cmode {
1362 my ($conf) = @_;
1363
1364 return $conf->{cmode} // $confdesc->{cmode}->{default};
1365}
1366
1367sub get_console_command {
1368 my ($vmid, $conf) = @_;
1369
1370 my $cmode = get_cmode($conf);
1371
1372 if ($cmode eq 'console') {
1373 return ['lxc-console', '-n', $vmid, '-t', 0];
1374 } elsif ($cmode eq 'tty') {
1375 return ['lxc-console', '-n', $vmid];
1376 } elsif ($cmode eq 'shell') {
1377 return ['lxc-attach', '--clear-env', '-n', $vmid];
1378 } else {
1379 die "internal error";
1380 }
1381}
1382
c325b32f
DM
1383sub get_primary_ips {
1384 my ($conf) = @_;
1385
1386 # return data from net0
cbb03fea 1387
27916659 1388 return undef if !defined($conf->{net0});
a16d94c8 1389 my $net = parse_lxc_network($conf->{net0});
c325b32f
DM
1390
1391 my $ipv4 = $net->{ip};
db78a181
WB
1392 if ($ipv4) {
1393 if ($ipv4 =~ /^(dhcp|manual)$/) {
1394 $ipv4 = undef
1395 } else {
1396 $ipv4 =~ s!/\d+$!!;
1397 }
1398 }
65e5eaa3 1399 my $ipv6 = $net->{ip6};
db78a181 1400 if ($ipv6) {
5f291c7d 1401 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
db78a181
WB
1402 $ipv6 = undef;
1403 } else {
1404 $ipv6 =~ s!/\d+$!!;
1405 }
1406 }
cbb03fea 1407
c325b32f
DM
1408 return ($ipv4, $ipv6);
1409}
148d1cb4 1410
b407293b
WB
1411sub delete_mountpoint_volume {
1412 my ($storage_cfg, $vmid, $volume) = @_;
1413
1414 # skip bind mounts and block devices
1415 if ($volume =~ m|^/|) {
1416 return;
1417 }
1418
1419 my ($vtype, $name, $owner) = PVE::Storage::parse_volname($storage_cfg, $volume);
1420 PVE::Storage::vdisk_free($storage_cfg, $volume) if $vmid == $owner;
1421}
ef241384 1422
27916659 1423sub destroy_lxc_container {
148d1cb4
DM
1424 my ($storage_cfg, $vmid, $conf) = @_;
1425
db8989e1
WB
1426 foreach_mountpoint($conf, sub {
1427 my ($ms, $mountpoint) = @_;
b407293b 1428 delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume});
db8989e1
WB
1429 });
1430
27916659
DM
1431 rmdir "/var/lib/lxc/$vmid/rootfs";
1432 unlink "/var/lib/lxc/$vmid/config";
1433 rmdir "/var/lib/lxc/$vmid";
1434 destroy_config($vmid);
1435
1436 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1437 #PVE::Tools::run_command($cmd);
148d1cb4 1438}
68fba17b 1439
ef241384 1440sub vm_stop_cleanup {
5fa890f0 1441 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
ef241384
DM
1442
1443 eval {
1444 if (!$keepActive) {
bf9d912c 1445
09aa32fd 1446 my $vollist = get_vm_volumes($conf);
a8b6b8a7 1447 PVE::Storage::deactivate_volumes($storage_cfg, $vollist);
ef241384
DM
1448 }
1449 };
1450 warn $@ if $@; # avoid errors - just warn
1451}
1452
93cdbbfb
AD
1453my $safe_num_ne = sub {
1454 my ($a, $b) = @_;
1455
1456 return 0 if !defined($a) && !defined($b);
1457 return 1 if !defined($a);
1458 return 1 if !defined($b);
1459
1460 return $a != $b;
1461};
1462
1463my $safe_string_ne = sub {
1464 my ($a, $b) = @_;
1465
1466 return 0 if !defined($a) && !defined($b);
1467 return 1 if !defined($a);
1468 return 1 if !defined($b);
1469
1470 return $a ne $b;
1471};
1472
1473sub update_net {
bedeaaf1 1474 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
93cdbbfb 1475
18862537
WB
1476 if ($newnet->{type} ne 'veth') {
1477 # for when there are physical interfaces
1478 die "cannot update interface of type $newnet->{type}";
1479 }
1480
1481 my $veth = "veth${vmid}i${netid}";
93cdbbfb
AD
1482 my $eth = $newnet->{name};
1483
18862537
WB
1484 if (my $oldnetcfg = $conf->{$opt}) {
1485 my $oldnet = parse_lxc_network($oldnetcfg);
1486
1487 if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
1488 &$safe_string_ne($oldnet->{name}, $newnet->{name})) {
93cdbbfb 1489
18862537 1490 PVE::Network::veth_delete($veth);
bedeaaf1 1491 delete $conf->{$opt};
706c9791 1492 write_config($vmid, $conf);
93cdbbfb 1493
18862537 1494 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
bedeaaf1 1495
18862537
WB
1496 } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
1497 &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
1498 &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
bedeaaf1 1499
18862537 1500 if ($oldnet->{bridge}) {
bedeaaf1 1501 PVE::Network::tap_unplug($veth);
18862537
WB
1502 foreach (qw(bridge tag firewall)) {
1503 delete $oldnet->{$_};
1504 }
1505 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1506 write_config($vmid, $conf);
bedeaaf1 1507 }
93cdbbfb 1508
18862537
WB
1509 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1510 foreach (qw(bridge tag firewall)) {
1511 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1512 }
1513 $conf->{$opt} = print_lxc_network($oldnet);
706c9791 1514 write_config($vmid, $conf);
93cdbbfb
AD
1515 }
1516 } else {
18862537 1517 hotplug_net($vmid, $conf, $opt, $newnet, $netid);
93cdbbfb
AD
1518 }
1519
bedeaaf1 1520 update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
93cdbbfb
AD
1521}
1522
1523sub hotplug_net {
18862537 1524 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
93cdbbfb 1525
18862537 1526 my $veth = "veth${vmid}i${netid}";
cbb03fea 1527 my $vethpeer = $veth . "p";
93cdbbfb
AD
1528 my $eth = $newnet->{name};
1529
1530 PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
1531 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
1532
cbb03fea 1533 # attach peer in container
93cdbbfb
AD
1534 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1535 PVE::Tools::run_command($cmd);
1536
cbb03fea 1537 # link up peer in container
93cdbbfb
AD
1538 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1539 PVE::Tools::run_command($cmd);
bedeaaf1 1540
18862537
WB
1541 my $done = { type => 'veth' };
1542 foreach (qw(bridge tag firewall hwaddr name)) {
1543 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1544 }
1545 $conf->{$opt} = print_lxc_network($done);
bedeaaf1 1546
706c9791 1547 write_config($vmid, $conf);
93cdbbfb
AD
1548}
1549
68a05bb3 1550sub update_ipconfig {
bedeaaf1
AD
1551 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1552
f2104b80 1553 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
bedeaaf1 1554
18862537 1555 my $optdata = parse_lxc_network($conf->{$opt});
84e0c123
WB
1556 my $deleted = [];
1557 my $added = [];
8d723477
WB
1558 my $nscmd = sub {
1559 my $cmdargs = shift;
1560 PVE::Tools::run_command(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
84e0c123 1561 };
8d723477 1562 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
2bfd1615 1563
84e0c123 1564 my $change_ip_config = sub {
f39002a6
DM
1565 my ($ipversion) = @_;
1566
1567 my $family_opt = "-$ipversion";
1568 my $suffix = $ipversion == 4 ? '' : $ipversion;
84e0c123
WB
1569 my $gw= "gw$suffix";
1570 my $ip= "ip$suffix";
bedeaaf1 1571
6178b0dd
WB
1572 my $newip = $newnet->{$ip};
1573 my $newgw = $newnet->{$gw};
1574 my $oldip = $optdata->{$ip};
1575
1576 my $change_ip = &$safe_string_ne($oldip, $newip);
1577 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
bedeaaf1 1578
84e0c123 1579 return if !$change_ip && !$change_gw;
68a05bb3 1580
84e0c123 1581 # step 1: add new IP, if this fails we cancel
292aff54
WB
1582 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1583 if ($change_ip && $is_real_ip) {
8d723477 1584 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
84e0c123
WB
1585 if (my $err = $@) {
1586 warn $err;
1587 return;
1588 }
bedeaaf1 1589 }
bedeaaf1 1590
84e0c123
WB
1591 # step 2: replace gateway
1592 # If this fails we delete the added IP and cancel.
1593 # If it succeeds we save the config and delete the old IP, ignoring
1594 # errors. The config is then saved.
1595 # Note: 'ip route replace' can add
1596 if ($change_gw) {
6178b0dd 1597 if ($newgw) {
292aff54
WB
1598 eval {
1599 if ($is_real_ip && !PVE::Network::is_ip_in_cidr($newgw, $newip, $ipversion)) {
1600 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1601 }
1602 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1603 };
84e0c123
WB
1604 if (my $err = $@) {
1605 warn $err;
1606 # the route was not replaced, the old IP is still available
1607 # rollback (delete new IP) and cancel
1608 if ($change_ip) {
8d723477 1609 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
84e0c123
WB
1610 warn $@ if $@; # no need to die here
1611 }
1612 return;
1613 }
1614 } else {
8d723477 1615 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
84e0c123
WB
1616 # if the route was not deleted, the guest might have deleted it manually
1617 # warn and continue
1618 warn $@ if $@;
1619 }
2bfd1615 1620 }
2bfd1615 1621
6178b0dd 1622 # from this point on we save the configuration
84e0c123 1623 # step 3: delete old IP ignoring errors
6178b0dd 1624 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
8d723477
WB
1625 # We need to enable promote_secondaries, otherwise our newly added
1626 # address will be removed along with the old one.
1627 my $promote = 0;
1628 eval {
1629 if ($ipversion == 4) {
1630 &$nscmd({ outfunc => sub { $promote = int(shift) } },
1631 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1632 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1633 }
1634 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1635 };
84e0c123 1636 warn $@ if $@; # no need to die here
8d723477
WB
1637
1638 if ($ipversion == 4) {
1639 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1640 }
bedeaaf1
AD
1641 }
1642
84e0c123
WB
1643 foreach my $property ($ip, $gw) {
1644 if ($newnet->{$property}) {
1645 $optdata->{$property} = $newnet->{$property};
1646 } else {
1647 delete $optdata->{$property};
1648 }
bedeaaf1 1649 }
18862537 1650 $conf->{$opt} = print_lxc_network($optdata);
706c9791 1651 write_config($vmid, $conf);
84e0c123
WB
1652 $lxc_setup->setup_network($conf);
1653 };
bedeaaf1 1654
f39002a6
DM
1655 &$change_ip_config(4);
1656 &$change_ip_config(6);
489e960d
WL
1657
1658}
1659
a92f66c9
WL
1660# Internal snapshots
1661
1662# NOTE: Snapshot create/delete involves several non-atomic
1663# action, and can take a long time.
1664# So we try to avoid locking the file and use 'lock' variable
1665# inside the config file instead.
1666
1667my $snapshot_copy_config = sub {
1668 my ($source, $dest) = @_;
1669
1670 foreach my $k (keys %$source) {
1671 next if $k eq 'snapshots';
09d3ec42
DM
1672 next if $k eq 'snapstate';
1673 next if $k eq 'snaptime';
1674 next if $k eq 'vmstate';
1675 next if $k eq 'lock';
a92f66c9 1676 next if $k eq 'digest';
09d3ec42 1677 next if $k eq 'description';
a92f66c9
WL
1678
1679 $dest->{$k} = $source->{$k};
1680 }
1681};
1682
1683my $snapshot_prepare = sub {
1684 my ($vmid, $snapname, $comment) = @_;
1685
1686 my $snap;
1687
1688 my $updatefn = sub {
1689
1690 my $conf = load_config($vmid);
1691
bb1ac2de
DM
1692 die "you can't take a snapshot if it's a template\n"
1693 if is_template($conf);
1694
a92f66c9
WL
1695 check_lock($conf);
1696
09d3ec42 1697 $conf->{lock} = 'snapshot';
a92f66c9
WL
1698
1699 die "snapshot name '$snapname' already used\n"
1700 if defined($conf->{snapshots}->{$snapname});
1701
1702 my $storecfg = PVE::Storage::config();
1703 die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
1704
1705 $snap = $conf->{snapshots}->{$snapname} = {};
1706
1707 &$snapshot_copy_config($conf, $snap);
1708
09d3ec42
DM
1709 $snap->{'snapstate'} = "prepare";
1710 $snap->{'snaptime'} = time();
1711 $snap->{'description'} = $comment if $comment;
a92f66c9
WL
1712 $conf->{snapshots}->{$snapname} = $snap;
1713
706c9791 1714 write_config($vmid, $conf);
a92f66c9
WL
1715 };
1716
1717 lock_container($vmid, 10, $updatefn);
1718
1719 return $snap;
1720};
1721
1722my $snapshot_commit = sub {
1723 my ($vmid, $snapname) = @_;
1724
1725 my $updatefn = sub {
1726
1727 my $conf = load_config($vmid);
1728
1729 die "missing snapshot lock\n"
09d3ec42 1730 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
a92f66c9 1731
27916659 1732 die "snapshot '$snapname' does not exist\n"
a92f66c9
WL
1733 if !defined($conf->{snapshots}->{$snapname});
1734
1735 die "wrong snapshot state\n"
09d3ec42
DM
1736 if !($conf->{snapshots}->{$snapname}->{'snapstate'} &&
1737 $conf->{snapshots}->{$snapname}->{'snapstate'} eq "prepare");
a92f66c9 1738
09d3ec42
DM
1739 delete $conf->{snapshots}->{$snapname}->{'snapstate'};
1740 delete $conf->{lock};
1741 $conf->{parent} = $snapname;
a92f66c9 1742
706c9791 1743 write_config($vmid, $conf);
a92f66c9
WL
1744 };
1745
1746 lock_container($vmid, 10 ,$updatefn);
1747};
1748
1749sub has_feature {
1750 my ($feature, $conf, $storecfg, $snapname) = @_;
09d3ec42 1751
a92f66c9 1752 my $err;
09d3ec42 1753
8bf50651
DM
1754 foreach_mountpoint($conf, sub {
1755 my ($ms, $mountpoint) = @_;
1756
2c3ed8c4
DM
1757 return if $err; # skip further test
1758
8bf50651
DM
1759 $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $mountpoint->{volume}, $snapname);
1760
1761 # TODO: implement support for mountpoints
1762 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1763 if $ms ne 'rootfs';
1764 });
a92f66c9
WL
1765
1766 return $err ? 0 : 1;
1767}
1768
489e960d
WL
1769sub snapshot_create {
1770 my ($vmid, $snapname, $comment) = @_;
1771
a92f66c9
WL
1772 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1773
09d3ec42 1774 my $conf = load_config($vmid);
a92f66c9 1775
a92f66c9
WL
1776 my $running = check_running($vmid);
1777 eval {
1778 if ($running) {
4db769cf
WB
1779 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
1780 PVE::Tools::run_command(['/bin/sync']);
a92f66c9
WL
1781 };
1782
1783 my $storecfg = PVE::Storage::config();
706c9791 1784 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
09d3ec42 1785 my $volid = $rootinfo->{volume};
a92f66c9 1786
a92f66c9 1787 if ($running) {
4db769cf 1788 PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
a92f66c9 1789 };
489e960d 1790
a92f66c9
WL
1791 PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
1792 &$snapshot_commit($vmid, $snapname);
1793 };
1794 if(my $err = $@) {
31429832 1795 snapshot_delete($vmid, $snapname, 1);
a92f66c9
WL
1796 die "$err\n";
1797 }
68a05bb3
AD
1798}
1799
57ccb3f8
WL
1800sub snapshot_delete {
1801 my ($vmid, $snapname, $force) = @_;
1802
31429832
WL
1803 my $snap;
1804
1805 my $conf;
1806
1807 my $updatefn = sub {
1808
1809 $conf = load_config($vmid);
1810
bb1ac2de
DM
1811 die "you can't delete a snapshot if vm is a template\n"
1812 if is_template($conf);
1813
31429832
WL
1814 $snap = $conf->{snapshots}->{$snapname};
1815
1816 check_lock($conf);
1817
1818 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1819
09d3ec42 1820 $snap->{snapstate} = 'delete';
31429832 1821
706c9791 1822 write_config($vmid, $conf);
31429832
WL
1823 };
1824
1825 lock_container($vmid, 10, $updatefn);
1826
1827 my $storecfg = PVE::Storage::config();
1828
1829 my $del_snap = sub {
1830
1831 check_lock($conf);
1832
09d3ec42
DM
1833 if ($conf->{parent} eq $snapname) {
1834 if ($conf->{snapshots}->{$snapname}->{snapname}) {
1835 $conf->{parent} = $conf->{snapshots}->{$snapname}->{parent};
31429832 1836 } else {
09d3ec42 1837 delete $conf->{parent};
31429832
WL
1838 }
1839 }
1840
1841 delete $conf->{snapshots}->{$snapname};
1842
706c9791 1843 write_config($vmid, $conf);
31429832
WL
1844 };
1845
09d3ec42 1846 my $rootfs = $conf->{snapshots}->{$snapname}->{rootfs};
706c9791 1847 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42 1848 my $volid = $rootinfo->{volume};
31429832
WL
1849
1850 eval {
1851 PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
1852 };
1853 my $err = $@;
1854
1855 if(!$err || ($err && $force)) {
1856 lock_container($vmid, 10, $del_snap);
1857 if ($err) {
1858 die "Can't delete snapshot: $vmid $snapname $err\n";
1859 }
1860 }
57ccb3f8
WL
1861}
1862
723157f6
WL
1863sub snapshot_rollback {
1864 my ($vmid, $snapname) = @_;
1865
6860ba0c
WL
1866 my $storecfg = PVE::Storage::config();
1867
1868 my $conf = load_config($vmid);
1869
bb1ac2de
DM
1870 die "you can't rollback if vm is a template\n" if is_template($conf);
1871
6860ba0c
WL
1872 my $snap = $conf->{snapshots}->{$snapname};
1873
1874 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1875
09d3ec42 1876 my $rootfs = $snap->{rootfs};
706c9791 1877 my $rootinfo = parse_ct_mountpoint($rootfs);
09d3ec42
DM
1878 my $volid = $rootinfo->{volume};
1879
1880 PVE::Storage::volume_rollback_is_possible($storecfg, $volid, $snapname);
6860ba0c
WL
1881
1882 my $updatefn = sub {
1883
09d3ec42
DM
1884 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1885 if $snap->{snapstate};
6860ba0c
WL
1886
1887 check_lock($conf);
6860ba0c 1888
b935932a 1889 system("lxc-stop -n $vmid --kill") if check_running($vmid);
6860ba0c
WL
1890
1891 die "unable to rollback vm $vmid: vm is running\n"
1892 if check_running($vmid);
1893
09d3ec42 1894 $conf->{lock} = 'rollback';
6860ba0c
WL
1895
1896 my $forcemachine;
1897
1898 # copy snapshot config to current config
1899
1900 my $tmp_conf = $conf;
1901 &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
6860ba0c 1902 $conf->{snapshots} = $tmp_conf->{snapshots};
09d3ec42
DM
1903 delete $conf->{snaptime};
1904 delete $conf->{snapname};
1905 $conf->{parent} = $snapname;
6860ba0c 1906
706c9791 1907 write_config($vmid, $conf);
6860ba0c
WL
1908 };
1909
1910 my $unlockfn = sub {
09d3ec42 1911 delete $conf->{lock};
706c9791 1912 write_config($vmid, $conf);
6860ba0c
WL
1913 };
1914
1915 lock_container($vmid, 10, $updatefn);
1916
09d3ec42 1917 PVE::Storage::volume_snapshot_rollback($storecfg, $volid, $snapname);
6860ba0c
WL
1918
1919 lock_container($vmid, 5, $unlockfn);
723157f6 1920}
b935932a 1921
bb1ac2de
DM
1922sub template_create {
1923 my ($vmid, $conf) = @_;
1924
1925 my $storecfg = PVE::Storage::config();
1926
706c9791 1927 my $rootinfo = parse_ct_mountpoint($conf->{rootfs});
bb1ac2de
DM
1928 my $volid = $rootinfo->{volume};
1929
1930 die "Template feature is not available for '$volid'\n"
1931 if !PVE::Storage::volume_has_feature($storecfg, 'template', $volid);
1932
1933 PVE::Storage::activate_volumes($storecfg, [$volid]);
1934
1935 my $template_volid = PVE::Storage::vdisk_create_base($storecfg, $volid);
1936 $rootinfo->{volume} = $template_volid;
4fee75fd 1937 $conf->{rootfs} = print_ct_mountpoint($rootinfo, 1);
bb1ac2de
DM
1938
1939 write_config($vmid, $conf);
1940}
1941
1942sub is_template {
1943 my ($conf) = @_;
1944
1945 return 1 if defined $conf->{template} && $conf->{template} == 1;
1946}
1947
9622e848
DM
1948sub mountpoint_names {
1949 my ($reverse) = @_;
ced7fddb 1950
9622e848 1951 my @names = ('rootfs');
eaebef36
DM
1952
1953 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
9622e848
DM
1954 push @names, "mp$i";
1955 }
1956
1957 return $reverse ? reverse @names : @names;
1958}
1959
3c9dbfa9
WB
1960# The container might have *different* symlinks than the host. realpath/abs_path
1961# use the actual filesystem to resolve links.
1962sub sanitize_mountpoint {
1963 my ($mp) = @_;
1964 $mp = '/' . $mp; # we always start with a slash
1965 $mp =~ s@/{2,}@/@g; # collapse sequences of slashes
1966 $mp =~ s@/\./@@g; # collapse /./
1967 $mp =~ s@/\.(/)?$@$1@; # collapse a trailing /. or /./
1968 $mp =~ s@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1969 $mp =~ s@/\.\.(/)?$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1970 return $mp;
1971}
1972
9622e848
DM
1973sub foreach_mountpoint_full {
1974 my ($conf, $reverse, $func) = @_;
1975
1976 foreach my $key (mountpoint_names($reverse)) {
1977 my $value = $conf->{$key};
1978 next if !defined($value);
ca7feb1a
WB
1979 my $mountpoint = parse_ct_mountpoint($value, 1);
1980 next if !defined($mountpoint);
3c9dbfa9
WB
1981
1982 # just to be sure: rootfs is /
1983 my $path = $key eq 'rootfs' ? '/' : $mountpoint->{mp};
1984 $mountpoint->{mp} = sanitize_mountpoint($path);
1985
1986 $path = $mountpoint->{volume};
1987 $mountpoint->{volume} = sanitize_mountpoint($path) if $path =~ m|^/|;
1988
eaebef36 1989 &$func($key, $mountpoint);
ced7fddb
AD
1990 }
1991}
1992
9622e848
DM
1993sub foreach_mountpoint {
1994 my ($conf, $func) = @_;
1995
1996 foreach_mountpoint_full($conf, 0, $func);
1997}
1998
1999sub foreach_mountpoint_reverse {
2000 my ($conf, $func) = @_;
2001
2002 foreach_mountpoint_full($conf, 1, $func);
2003}
2004
52389a07
DM
2005sub check_ct_modify_config_perm {
2006 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2007
2008 return 1 if $authuser ne 'root@pam';
2009
2010 foreach my $opt (@$key_list) {
2011
2012 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2013 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
e59a61ed 2014 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
52389a07
DM
2015 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2016 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2017 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2018 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2019 $opt eq 'searchdomain' || $opt eq 'hostname') {
2020 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2021 } else {
2022 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2023 }
2024 }
2025
2026 return 1;
2027}
2028
9622e848 2029sub umount_all {
da629848 2030 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
9622e848
DM
2031
2032 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2033 my $volid_list = get_vm_volumes($conf);
2034
2035 foreach_mountpoint_reverse($conf, sub {
2036 my ($ms, $mountpoint) = @_;
2037
2038 my $volid = $mountpoint->{volume};
2039 my $mount = $mountpoint->{mp};
2040
2041 return if !$volid || !$mount;
2042
d18f96b4 2043 my $mount_path = "$rootdir/$mount";
f845a93d 2044 $mount_path =~ s!/+!/!g;
9622e848 2045
228a5a1d
WL
2046 return if !PVE::ProcFSTools::is_mounted($mount_path);
2047
9622e848 2048 eval {
d18f96b4 2049 PVE::Tools::run_command(['umount', '-d', $mount_path]);
9622e848
DM
2050 };
2051 if (my $err = $@) {
2052 if ($noerr) {
2053 warn $err;
2054 } else {
2055 die $err;
2056 }
2057 }
2058 });
9622e848
DM
2059}
2060
2061sub mount_all {
7b49dfe0 2062 my ($vmid, $storage_cfg, $conf) = @_;
9622e848
DM
2063
2064 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
1adc7e53 2065 File::Path::make_path($rootdir);
9622e848
DM
2066
2067 my $volid_list = get_vm_volumes($conf);
2068 PVE::Storage::activate_volumes($storage_cfg, $volid_list);
2069
2070 eval {
9622e848
DM
2071 foreach_mountpoint($conf, sub {
2072 my ($ms, $mountpoint) = @_;
2073
2074 my $volid = $mountpoint->{volume};
2075 my $mount = $mountpoint->{mp};
2076
2077 return if !$volid || !$mount;
2078
2079 my $image_path = PVE::Storage::path($storage_cfg, $volid);
2080 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2081 PVE::Storage::parse_volname($storage_cfg, $volid);
2082
2083 die "unable to mount base volume - internal error" if $isBase;
2084
da629848 2085 mountpoint_mount($mountpoint, $rootdir, $storage_cfg);
9622e848
DM
2086 });
2087 };
2088 if (my $err = $@) {
2089 warn "mounting container failed - $err";
2090 umount_all($vmid, $storage_cfg, $conf, 1);
9622e848
DM
2091 }
2092
da629848 2093 return $rootdir;
9622e848
DM
2094}
2095
2096
b15c75fc 2097sub mountpoint_mount_path {
da629848 2098 my ($mountpoint, $storage_cfg, $snapname) = @_;
b15c75fc 2099
da629848 2100 return mountpoint_mount($mountpoint, undef, $storage_cfg, $snapname);
b15c75fc 2101}
cc6b0307 2102
2cfae16e
WB
2103my $check_mount_path = sub {
2104 my ($path) = @_;
2105 $path = File::Spec->canonpath($path);
2106 my $real = Cwd::realpath($path);
2107 if ($real ne $path) {
2108 die "mount path modified by symlink: $path != $real";
2109 }
2110};
2111
21f292ff
WB
2112sub query_loopdev {
2113 my ($path) = @_;
2114 my $found;
2115 my $parser = sub {
2116 my $line = shift;
2117 if ($line =~ m@^(/dev/loop\d+):@) {
2118 $found = $1;
2119 }
2120 };
2121 my $cmd = ['losetup', '--associated', $path];
2122 PVE::Tools::run_command($cmd, outfunc => $parser);
2123 return $found;
2124}
2125
b15c75fc 2126# use $rootdir = undef to just return the corresponding mount path
cc6b0307 2127sub mountpoint_mount {
da629848 2128 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
cc6b0307
AD
2129
2130 my $volid = $mountpoint->{volume};
2131 my $mount = $mountpoint->{mp};
b15c75fc 2132
cc6b0307
AD
2133 return if !$volid || !$mount;
2134
b15c75fc
DM
2135 my $mount_path;
2136
2137 if (defined($rootdir)) {
2138 $rootdir =~ s!/+$!!;
2139 $mount_path = "$rootdir/$mount";
f845a93d 2140 $mount_path =~ s!/+!/!g;
2cfae16e 2141 &$check_mount_path($mount_path);
b15c75fc 2142 File::Path::mkpath($mount_path);
116ce06f 2143 }
b15c75fc
DM
2144
2145 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
cc6b0307 2146
b15c75fc 2147 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
cc6b0307 2148
b15c75fc
DM
2149 if ($storage) {
2150
2151 my $scfg = PVE::Storage::storage_config($storage_cfg, $storage);
2152 my $path = PVE::Storage::path($storage_cfg, $volid, $snapname);
2153
2154 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2155 PVE::Storage::parse_volname($storage_cfg, $volid);
2156
c87b9dd8
DM
2157 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2158
b15c75fc 2159 if ($format eq 'subvol') {
30de33be
DM
2160 if ($mount_path) {
2161 if ($snapname) {
258dad4a 2162 if ($scfg->{type} ne 'zfspool') {
30de33be
DM
2163 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2164 }
30de33be 2165 }
258dad4a 2166 PVE::Tools::run_command(['mount', '-o', 'bind', $path, $mount_path]);
b15c75fc 2167 }
30de33be 2168 return wantarray ? ($path, 0) : $path;
c87b9dd8 2169 } elsif ($format eq 'raw' || $format eq 'iso') {
30de33be 2170 my $use_loopdev = 0;
da629848 2171 my @extra_opts;
b15c75fc 2172 if ($scfg->{path}) {
da629848 2173 push @extra_opts, '-o', 'loop';
30de33be 2174 $use_loopdev = 1;
23e3abef 2175 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm' || $scfg->{type} eq 'rbd') {
b15c75fc
DM
2176 # do nothing
2177 } else {
2178 die "unsupported storage type '$scfg->{type}'\n";
2179 }
30de33be 2180 if ($mount_path) {
c87b9dd8
DM
2181 if ($format eq 'iso') {
2182 PVE::Tools::run_command(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2183 } elsif ($isBase || defined($snapname)) {
9d7d4d30 2184 PVE::Tools::run_command(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
30de33be
DM
2185 } else {
2186 PVE::Tools::run_command(['mount', @extra_opts, $path, $mount_path]);
2187 }
b15c75fc 2188 }
30de33be 2189 return wantarray ? ($path, $use_loopdev) : $path;
b15c75fc
DM
2190 } else {
2191 die "unsupported image format '$format'\n";
2192 }
2193 } elsif ($volid =~ m|^/dev/.+|) {
2194 PVE::Tools::run_command(['mount', $volid, $mount_path]) if $mount_path;
30de33be 2195 return wantarray ? ($volid, 0) : $volid;
b15c75fc 2196 } elsif ($volid !~ m|^/dev/.+| && $volid =~ m|^/.+| && -d $volid) {
2cfae16e 2197 &$check_mount_path($volid);
b15c75fc 2198 PVE::Tools::run_command(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
30de33be 2199 return wantarray ? ($volid, 0) : $volid;
b15c75fc
DM
2200 }
2201
2202 die "unsupported storage";
cc6b0307
AD
2203}
2204
9205e9d0
AD
2205sub get_vm_volumes {
2206 my ($conf, $excludes) = @_;
2207
2208 my $vollist = [];
2209
706c9791 2210 foreach_mountpoint($conf, sub {
9205e9d0
AD
2211 my ($ms, $mountpoint) = @_;
2212
2213 return if $excludes && $ms eq $excludes;
2214
2215 my $volid = $mountpoint->{volume};
2216
2217 return if !$volid || $volid =~ m|^/|;
2218
2219 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2220 return if !$sid;
2221
2222 push @$vollist, $volid;
2223 });
2224
2225 return $vollist;
2226}
2227
6c871c36 2228sub mkfs {
d216e891 2229 my ($dev, $rootuid, $rootgid) = @_;
6c871c36 2230
d216e891
WB
2231 PVE::Tools::run_command(['mkfs.ext4', '-O', 'mmp',
2232 '-E', "root_owner=$rootuid:$rootgid",
2233 $dev]);
6c871c36
DM
2234}
2235
2236sub format_disk {
d216e891 2237 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
6c871c36
DM
2238
2239 if ($volid =~ m!^/dev/.+!) {
2240 mkfs($volid);
2241 return;
2242 }
2243
2244 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2245
2246 die "cannot format volume '$volid' with no storage\n" if !$storage;
2247
08ca136d
DM
2248 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
2249
6c871c36
DM
2250 my $path = PVE::Storage::path($storage_cfg, $volid);
2251
2252 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2253 PVE::Storage::parse_volname($storage_cfg, $volid);
2254
2255 die "cannot format volume '$volid' (format == $format)\n"
2256 if $format ne 'raw';
2257
d216e891 2258 mkfs($path, $rootuid, $rootgid);
6c871c36
DM
2259}
2260
2261sub destroy_disks {
2262 my ($storecfg, $vollist) = @_;
2263
2264 foreach my $volid (@$vollist) {
2265 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
2266 warn $@ if $@;
2267 }
2268}
2269
2270sub create_disks {
2271 my ($storecfg, $vmid, $settings, $conf) = @_;
2272
2273 my $vollist = [];
2274
2275 eval {
d216e891
WB
2276 my (undef, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf);
2277 my $chown_vollist = [];
2278
6c871c36
DM
2279 foreach_mountpoint($settings, sub {
2280 my ($ms, $mountpoint) = @_;
2281
2282 my $volid = $mountpoint->{volume};
2283 my $mp = $mountpoint->{mp};
2284
2285 my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1);
2286
2287 return if !$storage;
2288
2289 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
8ed5ff9d 2290 my ($storeid, $size_gb) = ($1, $2);
6c871c36 2291
8ed5ff9d 2292 my $size_kb = int(${size_gb}*1024) * 1024;
6c871c36
DM
2293
2294 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
2295 # fixme: use better naming ct-$vmid-disk-X.raw?
2296
2297 if ($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs') {
8ed5ff9d 2298 if ($size_kb > 0) {
6c871c36 2299 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw',
8ed5ff9d 2300 undef, $size_kb);
d216e891 2301 format_disk($storecfg, $volid, $rootuid, $rootgid);
6c871c36
DM
2302 } else {
2303 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
2304 undef, 0);
d216e891 2305 push @$chown_vollist, $volid;
6c871c36
DM
2306 }
2307 } elsif ($scfg->{type} eq 'zfspool') {
2308
2309 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'subvol',
8ed5ff9d 2310 undef, $size_kb);
d216e891 2311 push @$chown_vollist, $volid;
23e3abef 2312 } elsif ($scfg->{type} eq 'drbd' || $scfg->{type} eq 'lvm') {
6c871c36 2313
8ed5ff9d 2314 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
d216e891 2315 format_disk($storecfg, $volid, $rootuid, $rootgid);
6c871c36
DM
2316
2317 } elsif ($scfg->{type} eq 'rbd') {
2318
2319 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd};
8ed5ff9d 2320 $volid = PVE::Storage::vdisk_alloc($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
d216e891 2321 format_disk($storecfg, $volid, $rootuid, $rootgid);
6c871c36
DM
2322 } else {
2323 die "unable to create containers on storage type '$scfg->{type}'\n";
2324 }
2325 push @$vollist, $volid;
8ed5ff9d 2326 my $new_mountpoint = { volume => $volid, size => $size_kb*1024, mp => $mp };
ab4232be 2327 $conf->{$ms} = print_ct_mountpoint($new_mountpoint, $ms eq 'rootfs');
6c871c36
DM
2328 } else {
2329 # use specified/existing volid
2330 }
2331 });
d216e891
WB
2332
2333 PVE::Storage::activate_volumes($storecfg, $chown_vollist, undef);
2334 foreach my $volid (@$chown_vollist) {
2335 my $path = PVE::Storage::path($storecfg, $volid, undef);
2336 chown($rootuid, $rootgid, $path);
2337 }
2338 PVE::Storage::deactivate_volumes($storecfg, $chown_vollist, undef);
6c871c36
DM
2339 };
2340 # free allocated images on error
2341 if (my $err = $@) {
2342 destroy_disks($storecfg, $vollist);
2343 die $err;
2344 }
2345 return $vollist;
2346}
2347
68e8f3c5
DM
2348# bash completion helper
2349
2350sub complete_os_templates {
2351 my ($cmdname, $pname, $cvalue) = @_;
2352
2353 my $cfg = PVE::Storage::config();
2354
9e9bc3a6 2355 my $storeid;
68e8f3c5
DM
2356
2357 if ($cvalue =~ m/^([^:]+):/) {
2358 $storeid = $1;
2359 }
2360
2361 my $vtype = $cmdname eq 'restore' ? 'backup' : 'vztmpl';
2362 my $data = PVE::Storage::template_list($cfg, $storeid, $vtype);
2363
2364 my $res = [];
2365 foreach my $id (keys %$data) {
2366 foreach my $item (@{$data->{$id}}) {
2367 push @$res, $item->{volid} if defined($item->{volid});
2368 }
2369 }
2370
2371 return $res;
2372}
2373
68e8f3c5
DM
2374my $complete_ctid_full = sub {
2375 my ($running) = @_;
2376
2377 my $idlist = vmstatus();
2378
2379 my $active_hash = list_active_containers();
2380
2381 my $res = [];
2382
2383 foreach my $id (keys %$idlist) {
2384 my $d = $idlist->{$id};
2385 if (defined($running)) {
2386 next if $d->{template};
2387 next if $running && !$active_hash->{$id};
2388 next if !$running && $active_hash->{$id};
2389 }
2390 push @$res, $id;
2391
2392 }
2393 return $res;
2394};
2395
2396sub complete_ctid {
2397 return &$complete_ctid_full();
2398}
2399
2400sub complete_ctid_stopped {
2401 return &$complete_ctid_full(0);
2402}
2403
2404sub complete_ctid_running {
2405 return &$complete_ctid_full(1);
2406}
2407
c6a605f9
WB
2408sub parse_id_maps {
2409 my ($conf) = @_;
2410
2411 my $id_map = [];
2412 my $rootuid = 0;
2413 my $rootgid = 0;
2414
2415 my $lxc = $conf->{lxc};
2416 foreach my $entry (@$lxc) {
2417 my ($key, $value) = @$entry;
2418 next if $key ne 'lxc.id_map';
2419 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2420 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2421 push @$id_map, [$type, $ct, $host, $length];
2422 if ($ct == 0) {
2423 $rootuid = $host if $type eq 'u';
2424 $rootgid = $host if $type eq 'g';
2425 }
2426 } else {
2427 die "failed to parse id_map: $value\n";
2428 }
2429 }
2430
2431 if (!@$id_map && $conf->{unprivileged}) {
2432 # Should we read them from /etc/subuid?
2433 $id_map = [ ['u', '0', '100000', '65536'],
2434 ['g', '0', '100000', '65536'] ];
2435 $rootuid = $rootgid = 100000;
2436 }
2437
2438 return ($id_map, $rootuid, $rootgid);
2439}
2440
01dce99b
WB
2441sub userns_command {
2442 my ($id_map) = @_;
2443 if (@$id_map) {
2444 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];
2445 }
2446 return [];
2447}
2448
f76a2828 24491;