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