]> git.proxmox.com Git - pve-container.git/blame - src/PVE/LXC/Config.pm
correctly set unlimited cpulimit at runtime
[pve-container.git] / src / PVE / LXC / Config.pm
CommitLineData
67afe46e
FG
1package PVE::LXC::Config;
2
3use strict;
4use warnings;
5
6use PVE::AbstractConfig;
7use PVE::Cluster qw(cfs_register_file);
8use PVE::INotify;
9use PVE::JSONSchema qw(get_standard_option);
10use PVE::Tools;
11
12use base qw(PVE::AbstractConfig);
13
14my $nodename = PVE::INotify::nodename();
15my $lock_handles = {};
16my $lockdir = "/run/lock/lxc";
17mkdir $lockdir;
18mkdir "/etc/pve/nodes/$nodename/lxc";
19my $MAX_MOUNT_POINTS = 10;
20my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
21
22# BEGIN implemented abstract methods from PVE::AbstractConfig
23
24sub guest_type {
25 return "CT";
26}
27
4518000b
FG
28sub __config_max_unused_disks {
29 my ($class) = @_;
30
31 return $MAX_UNUSED_DISKS;
32}
33
67afe46e
FG
34sub config_file_lock {
35 my ($class, $vmid) = @_;
36
37 return "$lockdir/pve-config-${vmid}.lock";
38}
39
40sub cfs_config_path {
41 my ($class, $vmid, $node) = @_;
42
43 $node = $nodename if !$node;
44 return "nodes/$node/lxc/$vmid.conf";
45}
46
1a8269bc 47sub mountpoint_backup_enabled {
ec7f0f09 48 my ($class, $mp_key, $mountpoint) = @_;
1a8269bc
DM
49
50 return 1 if $mp_key eq 'rootfs';
51
f59c9670
FG
52 return 0 if $mountpoint->{type} ne 'volume';
53
1a8269bc
DM
54 return 1 if $mountpoint->{backup};
55
56 return 0;
57}
58
4518000b
FG
59sub has_feature {
60 my ($class, $feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
61 my $err;
62
d250604f 63 $class->foreach_mountpoint($conf, sub {
4518000b
FG
64 my ($ms, $mountpoint) = @_;
65
66 return if $err; # skip further test
ec7f0f09 67 return if $backup_only && !$class->mountpoint_backup_enabled($ms, $mountpoint);
4518000b
FG
68
69 $err = 1
70 if !PVE::Storage::volume_has_feature($storecfg, $feature,
71 $mountpoint->{volume},
72 $snapname, $running);
73 });
74
75 return $err ? 0 : 1;
76}
77
78sub __snapshot_save_vmstate {
79 my ($class, $vmid, $conf, $snapname, $storecfg) = @_;
80 die "implement me - snapshot_save_vmstate\n";
81}
82
83sub __snapshot_check_running {
84 my ($class, $vmid) = @_;
85 return PVE::LXC::check_running($vmid);
86}
87
88sub __snapshot_check_freeze_needed {
89 my ($class, $vmid, $config, $save_vmstate) = @_;
90
91 my $ret = $class->__snapshot_check_running($vmid);
92 return ($ret, $ret);
93}
94
95sub __snapshot_freeze {
96 my ($class, $vmid, $unfreeze) = @_;
97
98 if ($unfreeze) {
99 eval { PVE::Tools::run_command(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
100 warn $@ if $@;
101 } else {
102 PVE::Tools::run_command(['/usr/bin/lxc-freeze', '-n', $vmid]);
103 PVE::LXC::sync_container_namespace($vmid);
104 }
105}
106
107sub __snapshot_create_vol_snapshot {
108 my ($class, $vmid, $ms, $mountpoint, $snapname) = @_;
109
110 my $storecfg = PVE::Storage::config();
111
1a8269bc 112 return if $snapname eq 'vzdump' &&
ec7f0f09 113 !$class->mountpoint_backup_enabled($ms, $mountpoint);
1a8269bc 114
4518000b
FG
115 PVE::Storage::volume_snapshot($storecfg, $mountpoint->{volume}, $snapname);
116}
117
118sub __snapshot_delete_remove_drive {
119 my ($class, $snap, $remove_drive) = @_;
120
121 if ($remove_drive eq 'vmstate') {
122 die "implement me - saving vmstate\n";
123 } else {
124 my $value = $snap->{$remove_drive};
1b4cf758 125 my $mountpoint = $remove_drive eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
4518000b 126 delete $snap->{$remove_drive};
d103721f
FG
127
128 $class->add_unused_volume($snap, $mountpoint->{volume})
129 if ($mountpoint->{type} eq 'volume');
4518000b
FG
130 }
131}
132
133sub __snapshot_delete_vmstate_file {
134 my ($class, $snap, $force) = @_;
135
136 die "implement me - saving vmstate\n";
137}
138
139sub __snapshot_delete_vol_snapshot {
a8e9a4ea 140 my ($class, $vmid, $ms, $mountpoint, $snapname, $unused) = @_;
4518000b 141
d103721f
FG
142 return if $snapname eq 'vzdump' &&
143 !$class->mountpoint_backup_enabled($ms, $mountpoint);
144
4518000b
FG
145 my $storecfg = PVE::Storage::config();
146 PVE::Storage::volume_snapshot_delete($storecfg, $mountpoint->{volume}, $snapname);
a8e9a4ea 147 push @$unused, $mountpoint->{volume};
4518000b
FG
148}
149
150sub __snapshot_rollback_vol_possible {
151 my ($class, $mountpoint, $snapname) = @_;
152
153 my $storecfg = PVE::Storage::config();
154 PVE::Storage::volume_rollback_is_possible($storecfg, $mountpoint->{volume}, $snapname);
155}
156
157sub __snapshot_rollback_vol_rollback {
158 my ($class, $mountpoint, $snapname) = @_;
159
160 my $storecfg = PVE::Storage::config();
161 PVE::Storage::volume_snapshot_rollback($storecfg, $mountpoint->{volume}, $snapname);
162}
163
164sub __snapshot_rollback_vm_stop {
165 my ($class, $vmid) = @_;
166
167 PVE::Tools::run_command(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
168 if $class->__snapshot_check_running($vmid);
169}
170
171sub __snapshot_rollback_vm_start {
172 my ($class, $vmid, $vmstate, $forcemachine);
173
174 die "implement me - save vmstate\n";
175}
176
a8656869
FG
177sub __snapshot_rollback_get_unused {
178 my ($class, $conf, $snap) = @_;
179
180 my $unused = [];
181
182 $class->__snapshot_foreach_volume($conf, sub {
183 my ($vs, $volume) = @_;
184
185 return if $volume->{type} ne 'volume';
186
187 my $found = 0;
188 my $volid = $volume->{volume};
189
190 $class->__snapshot_foreach_volume($snap, sub {
191 my ($ms, $mountpoint) = @_;
192
193 return if $found;
194 return if ($mountpoint->{type} ne 'volume');
195
196 $found = 1
197 if ($mountpoint->{volume} && $mountpoint->{volume} eq $volid);
198 });
199
200 push @$unused, $volid if !$found;
201 });
202
203 return $unused;
204}
205
4518000b
FG
206sub __snapshot_foreach_volume {
207 my ($class, $conf, $func) = @_;
208
d250604f 209 $class->foreach_mountpoint($conf, $func);
4518000b
FG
210}
211
67afe46e
FG
212# END implemented abstract methods from PVE::AbstractConfig
213
1b4cf758
FG
214# BEGIN JSON config code
215
216cfs_register_file('/lxc/', \&parse_pct_config, \&write_pct_config);
217
218my $rootfs_desc = {
219 volume => {
220 type => 'string',
221 default_key => 1,
222 format => 'pve-lxc-mp-string',
223 format_description => 'volume',
224 description => 'Volume, device or directory to mount into the container.',
225 },
1b4cf758
FG
226 size => {
227 type => 'string',
228 format => 'disk-size',
229 format_description => 'DiskSize',
230 description => 'Volume size (read only value).',
231 optional => 1,
232 },
233 acl => {
234 type => 'boolean',
1b4cf758
FG
235 description => 'Explicitly enable or disable ACL support.',
236 optional => 1,
237 },
238 ro => {
239 type => 'boolean',
fd0e8880 240 description => 'Read-only mountpoint',
1b4cf758
FG
241 optional => 1,
242 },
243 quota => {
244 type => 'boolean',
1b4cf758
FG
245 description => 'Enable user quotas inside the container (not supported with zfs subvolumes)',
246 optional => 1,
247 },
248};
249
250PVE::JSONSchema::register_standard_option('pve-ct-rootfs', {
251 type => 'string', format => $rootfs_desc,
252 description => "Use volume as container root.",
253 optional => 1,
254});
255
256PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', {
257 description => "The name of the snapshot.",
258 type => 'string', format => 'pve-configid',
259 maxLength => 40,
260});
261
262my $confdesc = {
263 lock => {
264 optional => 1,
265 type => 'string',
266 description => "Lock/unlock the VM.",
267 enum => [qw(migrate backup snapshot rollback)],
268 },
269 onboot => {
270 optional => 1,
271 type => 'boolean',
272 description => "Specifies whether a VM will be started during system bootup.",
273 default => 0,
274 },
275 startup => get_standard_option('pve-startup-order'),
276 template => {
277 optional => 1,
278 type => 'boolean',
279 description => "Enable/disable Template.",
280 default => 0,
281 },
282 arch => {
283 optional => 1,
284 type => 'string',
285 enum => ['amd64', 'i386'],
286 description => "OS architecture type.",
287 default => 'amd64',
288 },
289 ostype => {
290 optional => 1,
291 type => 'string',
ed027b58 292 enum => [qw(debian ubuntu centos fedora opensuse archlinux alpine gentoo unmanaged)],
1b4cf758
FG
293 description => "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
294 },
295 console => {
296 optional => 1,
297 type => 'boolean',
298 description => "Attach a console device (/dev/console) to the container.",
299 default => 1,
300 },
301 tty => {
302 optional => 1,
303 type => 'integer',
304 description => "Specify the number of tty available to the container",
305 minimum => 0,
306 maximum => 6,
307 default => 2,
308 },
309 cpulimit => {
310 optional => 1,
311 type => 'number',
064529c3 312 description => "Limit of CPU usage.\n\nNOTE: If the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
1b4cf758
FG
313 minimum => 0,
314 maximum => 128,
315 default => 0,
316 },
317 cpuunits => {
318 optional => 1,
319 type => 'integer',
320 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.",
321 minimum => 0,
322 maximum => 500000,
323 default => 1024,
324 },
325 memory => {
326 optional => 1,
327 type => 'integer',
328 description => "Amount of RAM for the VM in MB.",
329 minimum => 16,
330 default => 512,
331 },
332 swap => {
333 optional => 1,
334 type => 'integer',
335 description => "Amount of SWAP for the VM in MB.",
336 minimum => 0,
337 default => 512,
338 },
339 hostname => {
340 optional => 1,
341 description => "Set a host name for the container.",
342 type => 'string', format => 'dns-name',
343 maxLength => 255,
344 },
345 description => {
346 optional => 1,
347 type => 'string',
a069f163 348 description => "Container description. Only used on the configuration web interface.",
1b4cf758
FG
349 },
350 searchdomain => {
351 optional => 1,
352 type => 'string', format => 'dns-name-list',
353 description => "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
354 },
355 nameserver => {
356 optional => 1,
357 type => 'string', format => 'address-list',
358 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.",
359 },
360 rootfs => get_standard_option('pve-ct-rootfs'),
361 parent => {
362 optional => 1,
363 type => 'string', format => 'pve-configid',
364 maxLength => 40,
365 description => "Parent snapshot name. This is used internally, and should not be modified.",
366 },
367 snaptime => {
368 optional => 1,
369 description => "Timestamp for snapshots.",
370 type => 'integer',
371 minimum => 0,
372 },
373 cmode => {
374 optional => 1,
375 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).",
376 type => 'string',
377 enum => ['shell', 'console', 'tty'],
378 default => 'tty',
379 },
380 protection => {
381 optional => 1,
382 type => 'boolean',
383 description => "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
384 default => 0,
385 },
386 unprivileged => {
387 optional => 1,
388 type => 'boolean',
389 description => "Makes the container run as unprivileged user. (Should not be modified manually.)",
390 default => 0,
391 },
392};
393
394my $valid_lxc_conf_keys = {
395 'lxc.include' => 1,
396 'lxc.arch' => 1,
397 'lxc.utsname' => 1,
398 'lxc.haltsignal' => 1,
399 'lxc.rebootsignal' => 1,
400 'lxc.stopsignal' => 1,
401 'lxc.init_cmd' => 1,
402 'lxc.network.type' => 1,
403 'lxc.network.flags' => 1,
404 'lxc.network.link' => 1,
405 'lxc.network.mtu' => 1,
406 'lxc.network.name' => 1,
407 'lxc.network.hwaddr' => 1,
408 'lxc.network.ipv4' => 1,
409 'lxc.network.ipv4.gateway' => 1,
410 'lxc.network.ipv6' => 1,
411 'lxc.network.ipv6.gateway' => 1,
412 'lxc.network.script.up' => 1,
413 'lxc.network.script.down' => 1,
414 'lxc.pts' => 1,
415 'lxc.console.logfile' => 1,
416 'lxc.console' => 1,
417 'lxc.tty' => 1,
418 'lxc.devttydir' => 1,
419 'lxc.hook.autodev' => 1,
420 'lxc.autodev' => 1,
421 'lxc.kmsg' => 1,
422 'lxc.mount' => 1,
423 'lxc.mount.entry' => 1,
424 'lxc.mount.auto' => 1,
425 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
426 'lxc.rootfs.mount' => 1,
427 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
428 ', please use mountpoint options in the "rootfs" key',
429 # lxc.cgroup.*
430 'lxc.cap.drop' => 1,
431 'lxc.cap.keep' => 1,
432 'lxc.aa_profile' => 1,
433 'lxc.aa_allow_incomplete' => 1,
434 'lxc.se_context' => 1,
435 'lxc.seccomp' => 1,
436 'lxc.id_map' => 1,
437 'lxc.hook.pre-start' => 1,
438 'lxc.hook.pre-mount' => 1,
439 'lxc.hook.mount' => 1,
440 'lxc.hook.start' => 1,
441 'lxc.hook.stop' => 1,
442 'lxc.hook.post-stop' => 1,
443 'lxc.hook.clone' => 1,
444 'lxc.hook.destroy' => 1,
445 'lxc.loglevel' => 1,
446 'lxc.logfile' => 1,
447 'lxc.start.auto' => 1,
448 'lxc.start.delay' => 1,
449 'lxc.start.order' => 1,
450 'lxc.group' => 1,
451 'lxc.environment' => 1,
452};
453
5e5915c5 454our $netconf_desc = {
1b4cf758
FG
455 type => {
456 type => 'string',
457 optional => 1,
458 description => "Network interface type.",
459 enum => [qw(veth)],
460 },
461 name => {
462 type => 'string',
a069f163
DM
463 format_description => 'string',
464 description => 'Name of the network device as seen from inside the container. (lxc.network.name)',
1b4cf758
FG
465 pattern => '[-_.\w\d]+',
466 },
467 bridge => {
468 type => 'string',
a069f163 469 format_description => 'bridge',
1b4cf758
FG
470 description => 'Bridge to attach the network device to.',
471 pattern => '[-_.\w\d]+',
472 optional => 1,
473 },
474 hwaddr => {
475 type => 'string',
a069f163 476 format_description => "XX:XX:XX:XX:XX:XX",
e6f20294 477 description => 'The interface MAC address. This is dynamically allocated by default, but you can set that statically if needed, for example to always have the same link-local IPv6 address. (lxc.network.hwaddr)',
1b4cf758
FG
478 pattern => qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
479 optional => 1,
480 },
481 mtu => {
482 type => 'integer',
1b4cf758
FG
483 description => 'Maximum transfer unit of the interface. (lxc.network.mtu)',
484 minimum => 64, # minimum ethernet frame is 64 bytes
485 optional => 1,
486 },
487 ip => {
488 type => 'string',
489 format => 'pve-ipv4-config',
490 format_description => 'IPv4Format/CIDR',
491 description => 'IPv4 address in CIDR format.',
492 optional => 1,
493 },
494 gw => {
495 type => 'string',
496 format => 'ipv4',
497 format_description => 'GatewayIPv4',
498 description => 'Default gateway for IPv4 traffic.',
499 optional => 1,
500 },
501 ip6 => {
502 type => 'string',
503 format => 'pve-ipv6-config',
504 format_description => 'IPv6Format/CIDR',
505 description => 'IPv6 address in CIDR format.',
506 optional => 1,
507 },
508 gw6 => {
509 type => 'string',
510 format => 'ipv6',
511 format_description => 'GatewayIPv6',
512 description => 'Default gateway for IPv6 traffic.',
513 optional => 1,
514 },
515 firewall => {
516 type => 'boolean',
1b4cf758
FG
517 description => "Controls whether this interface's firewall rules should be used.",
518 optional => 1,
519 },
520 tag => {
521 type => 'integer',
6b202dd5
DM
522 minimum => 1,
523 maximum => 4094,
1b4cf758
FG
524 description => "VLAN tag for this interface.",
525 optional => 1,
526 },
527 trunks => {
528 type => 'string',
529 pattern => qr/\d+(?:;\d+)*/,
530 format_description => 'vlanid[;vlanid...]',
531 description => "VLAN ids to pass through the interface",
532 optional => 1,
533 },
380962c7
WB
534 rate => {
535 type => 'number',
536 format_description => 'mbps',
537 description => "Apply rate limiting to the interface",
538 optional => 1,
539 },
1b4cf758
FG
540};
541PVE::JSONSchema::register_format('pve-lxc-network', $netconf_desc);
542
543my $MAX_LXC_NETWORKS = 10;
544for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
545 $confdesc->{"net$i"} = {
546 optional => 1,
547 type => 'string', format => $netconf_desc,
548 description => "Specifies network interfaces for the container.",
549 };
550}
551
552PVE::JSONSchema::register_format('pve-lxc-mp-string', \&verify_lxc_mp_string);
553sub verify_lxc_mp_string {
554 my ($mp, $noerr) = @_;
555
556 # do not allow:
557 # /./ or /../
558 # /. or /.. at the end
559 # ../ at the beginning
560
561 if($mp =~ m@/\.\.?/@ ||
562 $mp =~ m@/\.\.?$@ ||
563 $mp =~ m@^\.\./@) {
564 return undef if $noerr;
565 die "$mp contains illegal character sequences\n";
566 }
567 return $mp;
568}
569
570my $mp_desc = {
571 %$rootfs_desc,
84820d40
DM
572 backup => {
573 type => 'boolean',
574 description => 'Whether to include the mountpoint in backups.',
fd0e8880
FG
575 verbose_description => 'Whether to include the mountpoint in backups '.
576 '(only used for volume mountpoints).',
84820d40
DM
577 optional => 1,
578 },
1b4cf758
FG
579 mp => {
580 type => 'string',
581 format => 'pve-lxc-mp-string',
582 format_description => 'Path',
52b6f941
FG
583 description => 'Path to the mountpoint as seen from inside the container '.
584 '(must not contain symlinks).',
fd0e8880 585 verbose_description => "Path to the mountpoint as seen from inside the container.\n\n".
52b6f941 586 "NOTE: Must not contain any symlinks for security reasons."
1b4cf758
FG
587 },
588};
589PVE::JSONSchema::register_format('pve-ct-mountpoint', $mp_desc);
590
591my $unuseddesc = {
592 optional => 1,
593 type => 'string', format => 'pve-volume-id',
2928e616 594 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
1b4cf758
FG
595};
596
597for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
598 $confdesc->{"mp$i"} = {
599 optional => 1,
600 type => 'string', format => $mp_desc,
2928e616 601 description => "Use volume as container mount point.",
1b4cf758
FG
602 optional => 1,
603 };
604}
605
606for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
607 $confdesc->{"unused$i"} = $unuseddesc;
608}
609
610sub parse_pct_config {
611 my ($filename, $raw) = @_;
612
613 return undef if !defined($raw);
614
615 my $res = {
616 digest => Digest::SHA::sha1_hex($raw),
617 snapshots => {},
618 };
619
620 $filename =~ m|/lxc/(\d+).conf$|
621 || die "got strange filename '$filename'";
622
623 my $vmid = $1;
624
625 my $conf = $res;
626 my $descr = '';
627 my $section = '';
628
629 my @lines = split(/\n/, $raw);
630 foreach my $line (@lines) {
631 next if $line =~ m/^\s*$/;
632
633 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
634 $section = $1;
635 $conf->{description} = $descr if $descr;
636 $descr = '';
637 $conf = $res->{snapshots}->{$section} = {};
638 next;
639 }
640
641 if ($line =~ m/^\#(.*)\s*$/) {
642 $descr .= PVE::Tools::decode_text($1) . "\n";
643 next;
644 }
645
646 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
647 my $key = $1;
648 my $value = $3;
649 my $validity = $valid_lxc_conf_keys->{$key} || 0;
650 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
651 push @{$conf->{lxc}}, [$key, $value];
652 } elsif (my $errmsg = $validity) {
653 warn "vm $vmid - $key: $errmsg\n";
654 } else {
655 warn "vm $vmid - unable to parse config: $line\n";
656 }
657 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
658 $descr .= PVE::Tools::decode_text($2);
659 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
660 $conf->{snapstate} = $1;
661 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
662 my $key = $1;
663 my $value = $2;
664 eval { $value = PVE::LXC::Config->check_type($key, $value); };
665 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
666 $conf->{$key} = $value;
667 } else {
668 warn "vm $vmid - unable to parse config: $line\n";
669 }
670 }
671
672 $conf->{description} = $descr if $descr;
673
674 delete $res->{snapstate}; # just to be sure
675
676 return $res;
677}
678
679sub write_pct_config {
680 my ($filename, $conf) = @_;
681
682 delete $conf->{snapstate}; # just to be sure
683
684 my $volidlist = PVE::LXC::Config->get_vm_volumes($conf);
685 my $used_volids = {};
686 foreach my $vid (@$volidlist) {
687 $used_volids->{$vid} = 1;
688 }
689
690 # remove 'unusedX' settings if the volume is still used
691 foreach my $key (keys %$conf) {
692 my $value = $conf->{$key};
693 if ($key =~ m/^unused/ && $used_volids->{$value}) {
694 delete $conf->{$key};
695 }
696 }
697
698 my $generate_raw_config = sub {
699 my ($conf) = @_;
700
701 my $raw = '';
702
703 # add description as comment to top of file
704 my $descr = $conf->{description} || '';
705 foreach my $cl (split(/\n/, $descr)) {
706 $raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
707 }
708
709 foreach my $key (sort keys %$conf) {
710 next if $key eq 'digest' || $key eq 'description' ||
711 $key eq 'pending' || $key eq 'snapshots' ||
712 $key eq 'snapname' || $key eq 'lxc';
713 my $value = $conf->{$key};
714 die "detected invalid newline inside property '$key'\n"
715 if $value =~ m/\n/;
716 $raw .= "$key: $value\n";
717 }
718
719 if (my $lxcconf = $conf->{lxc}) {
720 foreach my $entry (@$lxcconf) {
721 my ($k, $v) = @$entry;
722 $raw .= "$k: $v\n";
723 }
724 }
725
726 return $raw;
727 };
728
729 my $raw = &$generate_raw_config($conf);
730
731 foreach my $snapname (sort keys %{$conf->{snapshots}}) {
732 $raw .= "\n[$snapname]\n";
733 $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
734 }
735
736 return $raw;
737}
738
739sub update_pct_config {
740 my ($class, $vmid, $conf, $running, $param, $delete) = @_;
741
742 my @nohotplug;
743
744 my $new_disks = 0;
745 my @deleted_volumes;
746
747 my $rootdir;
748 if ($running) {
749 my $pid = PVE::LXC::find_lxc_pid($vmid);
750 $rootdir = "/proc/$pid/root";
751 }
752
753 my $hotplug_error = sub {
754 if ($running) {
755 push @nohotplug, @_;
756 return 1;
757 } else {
758 return 0;
759 }
760 };
761
762 if (defined($delete)) {
763 foreach my $opt (@$delete) {
764 if (!exists($conf->{$opt})) {
765 warn "no such option: $opt\n";
766 next;
767 }
768
e96a0ceb 769 if ($opt eq 'memory' || $opt eq 'rootfs') {
1b4cf758 770 die "unable to delete required option '$opt'\n";
e96a0ceb
DC
771 } elsif ($opt eq 'hostname') {
772 delete $conf->{$opt};
1b4cf758
FG
773 } elsif ($opt eq 'swap') {
774 delete $conf->{$opt};
775 PVE::LXC::write_cgroup_value("memory", $vmid,
776 "memory.memsw.limit_in_bytes", -1);
777 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
778 delete $conf->{$opt};
779 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
780 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
781 next if $hotplug_error->($opt);
782 delete $conf->{$opt};
d13b770f 783 } elsif ($opt eq 'cpulimit') {
213d70e6 784 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
d13b770f
WB
785 delete $conf->{$opt};
786 } elsif ($opt eq 'cpuunits') {
787 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
788 delete $conf->{$opt};
1b4cf758
FG
789 } elsif ($opt =~ m/^net(\d)$/) {
790 delete $conf->{$opt};
791 next if !$running;
792 my $netid = $1;
793 PVE::Network::veth_delete("veth${vmid}i$netid");
794 } elsif ($opt eq 'protection') {
795 delete $conf->{$opt};
796 } elsif ($opt =~ m/^unused(\d+)$/) {
797 next if $hotplug_error->($opt);
798 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
799 push @deleted_volumes, $conf->{$opt};
800 delete $conf->{$opt};
801 } elsif ($opt =~ m/^mp(\d+)$/) {
802 next if $hotplug_error->($opt);
803 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
804 my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
805 delete $conf->{$opt};
806 if ($mp->{type} eq 'volume') {
807 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
808 }
809 } elsif ($opt eq 'unprivileged') {
810 die "unable to delete read-only option: '$opt'\n";
811 } else {
812 die "implement me (delete: $opt)"
813 }
814 PVE::LXC::Config->write_config($vmid, $conf) if $running;
815 }
816 }
817
818 # There's no separate swap size to configure, there's memory and "total"
819 # memory (iow. memory+swap). This means we have to change them together.
820 my $wanted_memory = PVE::Tools::extract_param($param, 'memory');
821 my $wanted_swap = PVE::Tools::extract_param($param, 'swap');
822 if (defined($wanted_memory) || defined($wanted_swap)) {
823
824 my $old_memory = ($conf->{memory} || 512);
825 my $old_swap = ($conf->{swap} || 0);
826
827 $wanted_memory //= $old_memory;
828 $wanted_swap //= $old_swap;
829
830 my $total = $wanted_memory + $wanted_swap;
831 if ($running) {
832 my $old_total = $old_memory + $old_swap;
833 if ($total > $old_total) {
834 PVE::LXC::write_cgroup_value("memory", $vmid,
835 "memory.memsw.limit_in_bytes",
836 int($total*1024*1024));
837 PVE::LXC::write_cgroup_value("memory", $vmid,
838 "memory.limit_in_bytes",
839 int($wanted_memory*1024*1024));
840 } else {
841 PVE::LXC::write_cgroup_value("memory", $vmid,
842 "memory.limit_in_bytes",
843 int($wanted_memory*1024*1024));
844 PVE::LXC::write_cgroup_value("memory", $vmid,
845 "memory.memsw.limit_in_bytes",
846 int($total*1024*1024));
847 }
848 }
849 $conf->{memory} = $wanted_memory;
850 $conf->{swap} = $wanted_swap;
851
852 PVE::LXC::Config->write_config($vmid, $conf) if $running;
853 }
854
855 my $used_volids = {};
1b3213ae
FG
856 my $check_content_type = sub {
857 my ($mp) = @_;
858 my $sid = PVE::Storage::parse_volume_id($mp->{volume});
859 my $scfg = PVE::Storage::config();
860 my $storage_config = PVE::Storage::storage_config($scfg, $sid);
861 die "storage '$sid' does not allow content type 'rootdir' (Container)\n"
862 if !$storage_config->{content}->{rootdir};
863 };
1b4cf758
FG
864
865 foreach my $opt (keys %$param) {
866 my $value = $param->{$opt};
867 my $check_protection_msg = "can't update CT $vmid drive '$opt'";
f31bd6ae 868 if ($opt eq 'hostname' || $opt eq 'arch') {
1b4cf758
FG
869 $conf->{$opt} = $value;
870 } elsif ($opt eq 'onboot') {
871 $conf->{$opt} = $value ? 1 : 0;
872 } elsif ($opt eq 'startup') {
873 $conf->{$opt} = $value;
874 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
875 next if $hotplug_error->($opt);
876 $conf->{$opt} = $value;
877 } elsif ($opt eq 'nameserver') {
878 next if $hotplug_error->($opt);
879 my $list = PVE::LXC::verify_nameserver_list($value);
880 $conf->{$opt} = $list;
881 } elsif ($opt eq 'searchdomain') {
882 next if $hotplug_error->($opt);
883 my $list = PVE::LXC::verify_searchdomain_list($value);
884 $conf->{$opt} = $list;
885 } elsif ($opt eq 'cpulimit') {
115e5862
FG
886 if ($value == 0) {
887 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
888 } else {
889 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
890 }
1b4cf758
FG
891 $conf->{$opt} = $value;
892 } elsif ($opt eq 'cpuunits') {
893 $conf->{$opt} = $value;
894 PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
895 } elsif ($opt eq 'description') {
896 $conf->{$opt} = PVE::Tools::encode_text($value);
897 } elsif ($opt =~ m/^net(\d+)$/) {
898 my $netid = $1;
899 my $net = PVE::LXC::Config->parse_lxc_network($value);
900 if (!$running) {
901 $conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
902 } else {
903 PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
904 }
905 } elsif ($opt eq 'protection') {
906 $conf->{$opt} = $value ? 1 : 0;
907 } elsif ($opt =~ m/^mp(\d+)$/) {
908 next if $hotplug_error->($opt);
909 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
910 my $old = $conf->{$opt};
56d7fc1b
FG
911 my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
912 if ($mp->{type} eq 'volume') {
1b3213ae 913 &$check_content_type($mp);
56d7fc1b
FG
914 $used_volids->{$mp->{volume}} = 1;
915 }
1b4cf758
FG
916 $conf->{$opt} = $value;
917 if (defined($old)) {
918 my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
919 if ($mp->{type} eq 'volume') {
920 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
921 }
922 }
923 $new_disks = 1;
1b4cf758
FG
924 } elsif ($opt eq 'rootfs') {
925 next if $hotplug_error->($opt);
926 PVE::LXC::Config->check_protection($conf, $check_protection_msg);
927 my $old = $conf->{$opt};
928 $conf->{$opt} = $value;
56d7fc1b
FG
929 my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
930 if ($mp->{type} eq 'volume') {
1b3213ae 931 &$check_content_type($mp);
56d7fc1b
FG
932 $used_volids->{$mp->{volume}} = 1;
933 }
1b4cf758
FG
934 if (defined($old)) {
935 my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
936 if ($mp->{type} eq 'volume') {
937 PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
938 }
939 }
1d9369fd 940 $new_disks = 1;
1b4cf758
FG
941 } elsif ($opt eq 'unprivileged') {
942 die "unable to modify read-only option: '$opt'\n";
943 } elsif ($opt eq 'ostype') {
944 next if $hotplug_error->($opt);
945 $conf->{$opt} = $value;
946 } else {
947 die "implement me: $opt";
948 }
949 PVE::LXC::Config->write_config($vmid, $conf) if $running;
950 }
951
952 # Apply deletions and creations of new volumes
953 if (@deleted_volumes) {
954 my $storage_cfg = PVE::Storage::config();
955 foreach my $volume (@deleted_volumes) {
956 next if $used_volids->{$volume}; # could have been re-added, too
957 # also check for references in snapshots
958 next if $class->is_volume_in_use($conf, $volume, 1);
959 PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $volume);
960 }
961 }
962
963 if ($new_disks) {
964 my $storage_cfg = PVE::Storage::config();
965 PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
966 }
967
968 # This should be the last thing we do here
969 if ($running && scalar(@nohotplug)) {
970 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
971 }
972}
973
974sub check_type {
975 my ($class, $key, $value) = @_;
976
977 die "unknown setting '$key'\n" if !$confdesc->{$key};
978
979 my $type = $confdesc->{$key}->{type};
980
981 if (!defined($value)) {
982 die "got undefined value\n";
983 }
984
985 if ($value =~ m/[\n\r]/) {
986 die "property contains a line feed\n";
987 }
988
989 if ($type eq 'boolean') {
990 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
991 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
992 die "type check ('boolean') failed - got '$value'\n";
993 } elsif ($type eq 'integer') {
994 return int($1) if $value =~ m/^(\d+)$/;
995 die "type check ('integer') failed - got '$value'\n";
996 } elsif ($type eq 'number') {
997 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
998 die "type check ('number') failed - got '$value'\n";
999 } elsif ($type eq 'string') {
1000 if (my $fmt = $confdesc->{$key}->{format}) {
1001 PVE::JSONSchema::check_format($fmt, $value);
1002 return $value;
1003 }
1004 return $value;
1005 } else {
1006 die "internal error"
1007 }
1008}
1009
1010
1011# add JSON properties for create and set function
1012sub json_config_properties {
1013 my ($class, $prop) = @_;
1014
1015 foreach my $opt (keys %$confdesc) {
1016 next if $opt eq 'parent' || $opt eq 'snaptime';
1017 next if $prop->{$opt};
1018 $prop->{$opt} = $confdesc->{$opt};
1019 }
1020
1021 return $prop;
1022}
1023
1024sub __parse_ct_mountpoint_full {
1025 my ($class, $desc, $data, $noerr) = @_;
1026
1027 $data //= '';
1028
1029 my $res;
1030 eval { $res = PVE::JSONSchema::parse_property_string($desc, $data) };
1031 if ($@) {
1032 return undef if $noerr;
1033 die $@;
1034 }
1035
1036 if (defined(my $size = $res->{size})) {
1037 $size = PVE::JSONSchema::parse_size($size);
1038 if (!defined($size)) {
1039 return undef if $noerr;
1040 die "invalid size: $size\n";
1041 }
1042 $res->{size} = $size;
1043 }
1044
1045 $res->{type} = $class->classify_mountpoint($res->{volume});
1046
1047 return $res;
1048};
1049
1050sub parse_ct_rootfs {
1051 my ($class, $data, $noerr) = @_;
1052
1053 my $res = $class->__parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
1054
1055 $res->{mp} = '/' if defined($res);
1056
1057 return $res;
1058}
1059
1060sub parse_ct_mountpoint {
1061 my ($class, $data, $noerr) = @_;
1062
1063 return $class->__parse_ct_mountpoint_full($mp_desc, $data, $noerr);
1064}
1065
1066sub print_ct_mountpoint {
1067 my ($class, $info, $nomp) = @_;
1068 my $skip = [ 'type' ];
1069 push @$skip, 'mp' if $nomp;
1070 return PVE::JSONSchema::print_property_string($info, $mp_desc, $skip);
1071}
1072
1073sub print_lxc_network {
1074 my ($class, $net) = @_;
1075 return PVE::JSONSchema::print_property_string($net, $netconf_desc);
1076}
1077
1078sub parse_lxc_network {
1079 my ($class, $data) = @_;
1080
1081 my $res = {};
1082
1083 return $res if !$data;
1084
1085 $res = PVE::JSONSchema::parse_property_string($netconf_desc, $data);
1086
1087 $res->{type} = 'veth';
2f19133b
WB
1088 if (!$res->{hwaddr}) {
1089 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1090 $res->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1091 }
1b4cf758
FG
1092
1093 return $res;
1094}
1095
1096sub option_exists {
1097 my ($class, $name) = @_;
1098
1099 return defined($confdesc->{$name});
1100}
1101# END JSON config code
1102
d250604f
FG
1103sub classify_mountpoint {
1104 my ($class, $vol) = @_;
1105 if ($vol =~ m!^/!) {
1106 return 'device' if $vol =~ m!^/dev/!;
1107 return 'bind';
1108 }
1109 return 'volume';
1110}
1111
1112sub is_volume_in_use {
1113 my ($class, $config, $volid, $include_snapshots) = @_;
1114 my $used = 0;
1115
1116 $class->foreach_mountpoint($config, sub {
1117 my ($ms, $mountpoint) = @_;
1118 return if $used;
1119 $used = $mountpoint->{type} eq 'volume' && $mountpoint->{volume} eq $volid;
1120 });
1121
1122 my $snapshots = $config->{snapshots};
1123 if ($include_snapshots && $snapshots) {
1124 foreach my $snap (keys %$snapshots) {
1125 $used ||= $class->is_volume_in_use($snapshots->{$snap}, $volid);
1126 }
1127 }
1128
1129 return $used;
1130}
1131
1132sub has_dev_console {
1133 my ($class, $conf) = @_;
1134
1135 return !(defined($conf->{console}) && !$conf->{console});
1136}
1137
1b4cf758
FG
1138sub get_tty_count {
1139 my ($class, $conf) = @_;
1140
1141 return $conf->{tty} // $confdesc->{tty}->{default};
1142}
1143
1144sub get_cmode {
1145 my ($class, $conf) = @_;
1146
1147 return $conf->{cmode} // $confdesc->{cmode}->{default};
1148}
1149
d250604f
FG
1150sub mountpoint_names {
1151 my ($class, $reverse) = @_;
1152
1153 my @names = ('rootfs');
1154
1155 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1156 push @names, "mp$i";
1157 }
1158
1159 return $reverse ? reverse @names : @names;
1160}
1161
1162sub foreach_mountpoint_full {
8915b450 1163 my ($class, $conf, $reverse, $func, @param) = @_;
d250604f
FG
1164
1165 foreach my $key ($class->mountpoint_names($reverse)) {
1166 my $value = $conf->{$key};
1167 next if !defined($value);
1b4cf758 1168 my $mountpoint = $key eq 'rootfs' ? $class->parse_ct_rootfs($value, 1) : $class->parse_ct_mountpoint($value, 1);
d250604f
FG
1169 next if !defined($mountpoint);
1170
8915b450 1171 &$func($key, $mountpoint, @param);
d250604f
FG
1172 }
1173}
1174
1175sub foreach_mountpoint {
8915b450 1176 my ($class, $conf, $func, @param) = @_;
d250604f 1177
8915b450 1178 $class->foreach_mountpoint_full($conf, 0, $func, @param);
d250604f
FG
1179}
1180
1181sub foreach_mountpoint_reverse {
8915b450 1182 my ($class, $conf, $func, @param) = @_;
d250604f 1183
8915b450 1184 $class->foreach_mountpoint_full($conf, 1, $func, @param);
d250604f
FG
1185}
1186
1187sub get_vm_volumes {
1188 my ($class, $conf, $excludes) = @_;
1189
1190 my $vollist = [];
1191
1192 $class->foreach_mountpoint($conf, sub {
1193 my ($ms, $mountpoint) = @_;
1194
1195 return if $excludes && $ms eq $excludes;
1196
1197 my $volid = $mountpoint->{volume};
1198 return if !$volid || $mountpoint->{type} ne 'volume';
1199
1200 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
1201 return if !$sid;
1202
1203 push @$vollist, $volid;
1204 });
1205
1206 return $vollist;
1207}
1208
67afe46e 1209return 1;