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