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