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