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